Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 91 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ assert_that(people).extracting('first_name').contains('Fred','Bob')
assert_that(people).extracting('first_name').does_not_contain('Charlie')
```

Of couse `extracting` works with subclasses too...suppose we create a simple class hierarchy by creating a `Developer` subclass of `Person`, like this:
Of course `extracting` works with subclasses too...suppose we create a simple class hierarchy by creating a `Developer` subclass of `Person`, like this:

```py
class Developer(Person):
Expand Down Expand Up @@ -852,7 +852,7 @@ forgiving behavior, you can use `soft_fail()` which is collected like any other

### Snapshot Testing

Take a snapshot of a python data structure, store it on disk in JSON format, and automatically compare the latest data to the stored data on every test run. The snapshot testing features of `assertpy` are borrowed from [Jest](https://facebook.github.io/jest/), a well-kwown and powerful Javascript testing framework.
Take a snapshot of a python data structure, store it on disk in JSON format, and automatically compare the latest data to the stored data on every test run. The snapshot testing features of `assertpy` are borrowed from [Jest](https://facebook.github.io/jest/), a well-kwown and powerful Javascript testing framework. Snapshots require Python 3.

For example, snapshot the following dict:

Expand Down Expand Up @@ -909,7 +909,94 @@ assert_that({'a':1,'b':2,'c':3}).snapshot(path='my-custom-folder')

#### Snapshot Blackbox

Functional testing (which snapshot testing falls under) is very much blackbox testing. When something goes wrong, it's hard to pinpoint the issue, because they provide little *isolation*. On the plus side, snapshots can provide enormous *leverage* as a few well-place snapshots can strongly verify application state that would require dozens if not hundreds of unit tests.
Functional testing (which snapshot testing falls under) is very much blackbox testing. When something goes wrong, it's hard to pinpoint the issue, because functional tests provide little *isolation*. On the plus side, snapshots can provide enormous *leverage* as a few well-placed snapshot tests can strongly verify an application is working that would otherwise require dozens if not hundreds of unit tests.

### Extension System - adding custom assertions

Sometimes you want to add your own custom assertions to `assertpy`. This can be done using the `add_extension()` helper.

For example, we can write a custom `is_5()` assertion like this:

```py
from assertpy import add_extension

def is_5(self):
if self.val != 5:
self._err(f'{self.val} is NOT 5!')
return self

add_extension(is_5)
```

Once registered with `assertpy`, we can use our new assertion as expected:

```py
assert_that(5).is_5()
assert_that(6).is_5() # fails!
```

Of course, `is_5()` is only available in the test file where `add_extension()` is called. If you want better control of scope of your custom extensions, such as writing extensions once and using them in any test file, you'll need to use the test setup functionality of your test runner. With [pytest](http://pytest.org/latest/contents.html), you can just use a `conftest.py` file and a _fixture_.

For example, if your `conftest.py` is:

```py
import pytest
from assertpy import add_extension

def is_5(self):
if self.val != 5:
self._err(f'{self.val} is NOT 5!')
return self

@pytest.fixture(scope='module')
def my_extensions():
add_extension(is_5)
```

Then in any test method in any test file (like `test_foo.py` for example), you just pass in the fixture and all of your extensions are available, like this:

```py
from assertpy import assert_that

def test_foo(my_extensions):
assert_that(5).is_5()
assert_that(6).is_5() # fails!
```

#### Writing custom assertions

Here are some useful tips to help you write your own custom assertions:

1. Use `self.val` to get the _actual_ value to be tested.
2. It's better to test the negative and fail if true.
3. Fail by raising an `AssertionError`
4. Always use the `self._err()` helper to fail (and print your failure message).
5. Always `return self` to allow for chaining.

Putting it all together, here is another custom assertion example, but annotated with comments:

```py
def is_multiple_of(self, other):
# validate actual value - must be "integer" (aka int or long)
if isinstance(self.val, numbers.Integral) is False and self.val > 0:
# bad input is error, not an assertion fail, so raise error
raise TypeError('val must be a positive integer')

# validate expected value
if isinstance(other, numbers.Integral) is False and other > 0:
raise TypeError('given arg must be a positive integer')

# divide and compute remainder using divmod() built-in
_, rem = divmod(self.val, other)

# test the negative (is remainder non-zero?)
if rem > 0:
# non-zero remainder, so not multiple -> we fail!
self._err('Expected <%s> to be multiple of <%s>, but was not.' % (self.val, other))

# success, and return self to allow chaining
return self
```

### Chaining

Expand Down Expand Up @@ -944,7 +1031,7 @@ There are always a few new features in the works...if you'd like to help, check

All files are licensed under the BSD 3-Clause License as follows:

> Copyright (c) 2015-2018, Activision Publishing, Inc.
> Copyright (c) 2015-2019, Activision Publishing, Inc.
> All rights reserved.
>
> Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Expand Down
3 changes: 2 additions & 1 deletion assertpy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from __future__ import absolute_import
from .assertpy import assert_that, assert_warn, soft_assertions, contents_of, fail, soft_fail, __version__
from .assertpy import assert_that, assert_warn, soft_assertions, fail, soft_fail, add_extension, __version__
from .file import contents_of
Loading