Welcome to Mockify!¶
About Mockify¶
Mockify is a highly customizable and expressive mocking library for Python inspired by Google Mock C++ framework, but adopted to Python world.
Unlike tools like unittest.mock
, Mockify is based on expectations
that you record on your mocks before they are injected to code being
under test. Each expectation represents arguments the mock is expected to be
called with and provides sequence of actions the mock will do when called
with that arguments. Actions allow to set a value to be returned, exception
to be raised or just function to be called. Alternatively, if no actions
should take place, you can just say how many times the mock is expected to be
called. And all of these is provided by simple, expressive and easy to use
API.
Here’s a simple example:
from mockify.core import satisfied
from mockify.mock import Mock
from mockify.actions import Return
def invoke(func):
return func()
def test_invoke_calls_func_returning_hello_world():
func = Mock('func')
func.expect_call().will_once(Return('Hello, world!'))
with satisfied(func):
assert invoke(func) == 'Hello, world!'
I hope you’ll find this library useful.
User’s Guide¶
Installation¶
From PyPI using pipenv¶
If your project’s dependencies are managed by pipenv, simply proceed to your project’s directory and invoke following command:
$ pipenv install --dev mockify
That will install most recent version of the library and automatically add it into your project’s development dependencies.
From PyPI using virtualenv and pip¶
If you are using virtualenv in your project, just activate it and invoke following command:
$ pip install mockify
That will install most recent version of the library.
You can also add Mockify to your requirements.txt file if your project already has one. After that, you can install all dependencies at once using this command:
$ pip install -r requirements.txt
Directly from source using virtualenv and pip¶
You can also install Mockify directly from source code by simply invoking this command inside active virtual Python environment:
$ pip install git+https://gitlab.com/zef1r/mockify.git@[branch-or-tag]
This will allow you to install most recent version of the library that may not be released to PyPI yet. And also you will be able to install from any branch or tag.
Verifying installation¶
After installation you can print installed version of Mockify library using following command:
$ python -c "import mockify; print(mockify.__version__)"
That command will print version of installed Mockify library. If installation was not successful, the command will fail.
Now you should be able to start using Mockify.
Quickstart¶
Introduction¶
In this quickstart guide we are going to use test-driven development technique to write a class for parsing messages of some textual protocol for transferring bytes of data of known size. In this guide you will:
- get familiar with basic concepts of Mockify,
- learn how to create mocks and inject them to code being under test,
- learn how to record expectations and actions on that mocks,
- learn how to set expected call count on mocks,
- learn how to check if mocks were satisfied once test is ended,
- learn how to read some of Mockify assertions.
After going through this quickstart guide you will be able to use Mockify in your projects, but to learn even more you should also read Tutorial chapter, which covers some more advanced features. I hope you will enjoy Mockify.
Let’s start then!
The XYZ protocol¶
Imagine you work in a team which was given a task to design and write a library for transferring binary data over a wire between two peers. There are no any special requirements for chunking data, re-sending chunks, handling connection failures etc. The protocol must be very simple, easy to implement in different languages, which other teams will start implementing once design is done, and easy to extend in the future if needed. Moreover, there is a preference for this protocol to be textual, like HTTP. So your team starts with a brainstorming session and came out with following protocol design proposal:
MAGIC_BYTES | \n | Version | \n | len(PAYLOAD) | \n | PAYLOAD
The protocol was named XYZ and single message of the protocol is composed of following parts, separated with single newline character:
MAGIC_BYTES
The string
"XYZ"
with protocol name.Used to identify beginning of XYZ messages in byte stream coming from remote peer.
Version
Protocol version in string format.
Currently always
"1.0"
, but your team wants the protocol to be extensible in case when more features would have to be incorporated.len(PAYLOAD)
- Length of
PAYLOAD
part in bytes, represented in string format. PAYLOAD
Message payload.
These are actual bytes that are transferred.
You and your team have presented that design to other teams, the design was accepted, and now every team starts implementing the protocol. Your team is responsible for Python part.
The XYZReader class¶
Your team has decided to divide work into following independent flows:
- Implementing higher level StreamReader and StreamWriter classes for reading/writing bytes from/to underlying socket, but with few additional features like reading/writing exactly given amount of bytes (socket on its own does not guarantee that),
- Implementing protocol logic in form of XYZReader and XYZWriter classes that depend on stream readers and writers, accordingly.
The only common point between these two categories of classes is the interface between protocol logic and streams. After internal discussion you and your team agreed for following interfaces:
class StreamReader:
def read(self, count) -> bytes
"""Read exactly *count* data from underlying stream."""
def readline(self) -> bytes
"""Read data from underlying stream and stop once newline is
found.
Newline is also returned, as a last byte.
"""
class StreamWriter:
def write(self, buffer):
"""Write entire *buffer* to underlying stream."""
Now half of the team can work on implementation of those interfaces, while the other half - on implementation of protocol’s logic. You will be writing XYZReader class.
Writing first test¶
Step 0: Mocking StreamReader interface¶
You know that XYZReader class logic must somehow use StreamReader. So you’ve started with following draft:
class XYZReader:
def __init__(self, stream_reader):
self._stream_reader = stream_reader
def read(self):
return b'Hello world!'
To instantiate that class you need to pass something as a stream_reader parameter. You know how this interface looks like, but don’t have a real implementation, because it is under development by rest of your team. But you cannot wait until they’re done - you have to mock it. And here Mockify comes in to help you.
First you need to import mockify.mock.Mock
class:
from mockify.mock import Mock
This class can be used to mock things like functions, methods, calls via module, getters, setters and more. This is the only one class to create mocks. And now you can instantiate it into StreamReader mock by creating instance of Mock class giving it a name:
stream_reader = Mock('stream_reader')
As you can see, there is no interface defined yet. It will be defined soon. Now you can instantiate XYZReader class with the mock we’ve created earlier:
xyz_reader = XYZReader(stream_reader)
assert xyz_reader.read() == b'Hello world!'
And here’s complete test at this step:
from mockify.mock import Mock
def test_read_xyz_message():
stream_reader = Mock('stream_reader')
xyz_reader = XYZReader(stream_reader)
assert xyz_reader.read() == b'Hello world!'
Step 1: Reading magic bytes¶
Okay, you have first iteration ready, but in fact there is nothing really
interesting happening yet. Let’s now add some business logic. You know, that
first part of XYZ message is MAGIC_BYTES
string that should always be
"XYZ"
. To get first part of message from incoming payload you need to
read it from underlying StreamReader. And since we’ve used
newline-separated parts, we’ll be using readline() method. Here’s
XYZReader class supplied with code for reading MAGIC_BYTES
:
class XYZReader:
def __init__(self, stream_reader):
self._stream_reader = stream_reader
def read(self):
self._stream_reader.readline()
return b'Hello world!'
And now let’s run our test again. You’ll see that it fails with
mockify.exc.UninterestedCall
exception:
>>> test_read_xyz_message()
Traceback (most recent call last):
...
mockify.exc.UninterestedCall: No expectations recorded for mock:
at <doctest default[0]>:7
-------------------------
Called:
stream_reader.readline()
That exception is triggered when for called mock there are no expectations recorded. To make the test pass again you have to record expectation for stream_reader.readline() method on stream_reader mock. Expectations are recorded by calling expect_call() method with arguments (positional and/or keyword) you expect your mock to be called with. And that method has to be called on readline attribute of stream_reader mock object. Here’s complete solution:
from mockify.mock import Mock
def test_read_xyz_message():
stream_reader = Mock('stream_reader')
stream_reader.readline.expect_call()
xyz_reader = XYZReader(stream_reader)
assert xyz_reader.read() == b'Hello world!'
Step 2: Reading version¶
Now let’s go back to out XYZReader class and add instruction for reading
Version
part of XYZ protocol message:
class XYZReader:
def __init__(self, stream_reader):
self._stream_reader = stream_reader
def read(self):
self._stream_reader.readline() # read magic bytes
self._stream_reader.readline() # read version
return b'Hello world!'
If you now run the test again, you’ll see it passes. That’s not what we were expecting: we’ve changed the code, so the test should fail. But it doesn’t. And that is due to the fact that we are missing one additional assertion.
Step 3: Using satisfied() context manager¶
In Mockify not all assertion errors will be caused by invalid or unexpected mock calls. If the call to mock finds matching expectation, it runs it. And running expectation can be in some situations as trivial as just increasing call counter, with no side effects. And that is what happened in previous test.
To make your test verify all aspects of mocks provided by Mockify, you have
to check if mocks you were created are satisfied before your test ends. A
mock is said to be satisfied if all its expectations are consumed during
execution of tested code. Such check can be done in few ways, but this time
let’s use mockify.core.satisfied()
context manager:
from mockify.core import satisfied
from mockify.mock import Mock
def test_read_xyz_message():
stream_reader = Mock('stream_reader')
stream_reader.readline.expect_call()
xyz_reader = XYZReader(stream_reader)
with satisfied(stream_reader):
assert xyz_reader.read() == b'Hello world!'
This context manager is created with mock object(-s) as argument(-s) and
should wrap part of the test function where tested code is executed. If at
least one of given mocks have at least one expectation unsatisfied (i.e.
called less or more times than expected), then context manager fails with
mockify.exc.Unsatisfied
assertion. And that happens when our updated
test is run:
>>> test_read_xyz_message()
Traceback (most recent call last):
...
mockify.exc.Unsatisfied: Following expectation is not satisfied:
at <doctest default[0]>:6
-------------------------
Pattern:
stream_reader.readline()
Expected:
to be called once
Actual:
called twice
The error was caused by second call to stream_reader.readline() method (to read protocol version), but we have only one expectation recorded in our test. This time we know that test should be adjusted, but of course that could also mean (f.e. when test was passing before making changes) that tested code needs to be fixed.
Step 4: Using Expectation.times() method¶
Okay, we know that our expectation needs to be somehow extended to fix error from previous step. We can either double the expectation (i.e. copy and paste just below) or change expected call count, which is one by default. Let’s go with a second approach.
When you call expect_call(), special mockify.core.Expectation
object
is created and returned. That object has few methods that can be used to
refine the expectation. And one of these methods is
mockify.core.Expectation.times()
. Here’s our fixed test with
stream_reader.readline() expected to be called twice:
from mockify.core import satisfied
from mockify.mock import Mock
def test_read_xyz_message():
stream_reader = Mock('stream_reader')
stream_reader.readline.expect_call().times(2)
xyz_reader = XYZReader(stream_reader)
with satisfied(stream_reader):
assert xyz_reader.read() == b'Hello world!'
As you can see, the expectation clearly says that it is expected to be called twice. And now our test is running fine, so let’s go back to XYZReader class, because there are still two parts missing.
Step 5: Reading payload¶
So far we’ve read magic bytes and version of our XYZ protocol frame. In this section let’s speed up a bit and read two remaining parts at once: payload size and payload. Here’s updated XYZReader class:
class XYZReader:
def __init__(self, stream_reader):
self._stream_reader = stream_reader
def read(self):
self._stream_reader.readline() # read magic bytes
self._stream_reader.readline() # read version
payload_size = self._stream_reader.readline() # read payload size (1)
payload_size = payload_size.rstrip() # trim ending newline (which is included) (2)
payload_size = int(payload_size) # conversion to int (3)
return self._stream_reader.read(payload_size) # read payload (4)
Here we are once again calling readline() to get payload size as string ending with newline (1). Then ending newline is stripped (2) and payload size is converted to integer (3). Finally read() method is called, with calculated payload_size as an argument (4).
Now let’s try to run our test we fixed before. The test will fail with following error:
>>> test_read_xyz_message()
Traceback (most recent call last):
...
AttributeError: 'NoneType' object has no attribute 'rstrip'
It fails on (2), during test code execution, not during checking if
expectations are satisfied. This is caused by default value returned by
mocked call, which is None
- like for Python functions that does not
return any values. To make our test move forward we need to change that
default behavior.
Step 6: Introducing actions¶
Mockify provides so called actions, available via mockify.actions
module. Actions are simply special classes that are used to override default
behaviour of returning None
when mock is called. You record actions
directly on expectation object using one of two methods:
mockify.core.Expectation.will_once()
for recording chains of unique actions,- or
mockify.core.Expectation.will_repeatedly()
for recording so called repeated actions.
In this example we’ll cover use of first of that methods and also we’ll use
mockify.actions.Return
action for setting return value. Here’s a
fixed test:
from mockify.core import satisfied
from mockify.mock import Mock
from mockify.actions import Return
def test_read_xyz_message():
stream_reader = Mock('stream_reader')
stream_reader.readline.expect_call().times(2)
stream_reader.readline.expect_call().will_once(Return(b'12\n')) # (1)
xyz_reader = XYZReader(stream_reader)
with satisfied(stream_reader):
assert xyz_reader.read() == b'Hello world!'
We’ve added one more expectation on readline() (1) and recorded single
action to return b'12\n'
as mock’s return value. So when readline()
is called for the third time, recorded action is invoked, forcing it to
return given bytes. Of course the test will move forward now, but it will
fail again, but few lines later:
>>> test_read_xyz_message()
Traceback (most recent call last):
...
mockify.exc.UninterestedCall: No expectations recorded for mock:
at <doctest default[0]>:12
--------------------------
Called:
stream_reader.read(12)
Yes, that’s right - we did not record any expectations for read() method,
and mockify.exc.UninterestedCall
tells that. We need to fix that by
recording adequate expectation.
Step 7: Completing the test¶
Here’s our final complete and passing test with one last missing expectation recorded:
from mockify.core import satisfied
from mockify.mock import Mock
from mockify.actions import Return
def test_read_xyz_message():
stream_reader = Mock('stream_reader')
stream_reader.readline.expect_call().times(2)
stream_reader.readline.expect_call().will_once(Return(b'12\n')) # (1)
stream_reader.read.expect_call(12).will_once(Return(b'Hello world!')) # (2)
xyz_reader = XYZReader(stream_reader)
with satisfied(stream_reader):
assert xyz_reader.read() == b'Hello world!'
We’ve added read() expectation at (2). Note that this time it is expected to be called with an argument, which is the same as we’ve injected in (1), but converted to integer (as our tested code does).
Verifying magic bytes¶
So far we’ve written one test covering successful scenario of reading message from underlying stream. Let’s take a look at our XYZReader class we’ve developed:
class XYZReader:
def __init__(self, stream_reader):
self._stream_reader = stream_reader
def read(self):
self._stream_reader.readline() # read magic bytes (1)
self._stream_reader.readline() # read version (2)
payload_size = self._stream_reader.readline()
payload_size = payload_size.rstrip()
payload_size = int(payload_size)
return self._stream_reader.read(payload_size)
There are two NOK (not OK) scenarios missing:
- magic bytes verification (1),
- and version verification (2).
Let’s start by writing test that handles checking if magic bytes received are
equal to b"XYZ"
. We’ve decided to raise XYZError exception (not yet
declared) in case when magic bytes are different than expected. Here’s the
test:
import pytest
from mockify.mock import Mock
from mockify.actions import Return
def test_when_invalid_magic_bytes_received__then_xyz_error_is_raised():
stream_reader = Mock('stream_reader')
stream_reader.readline.expect_call().will_once(Return(b'ABC\n'))
xyz_reader = XYZReader(stream_reader)
with pytest.raises(XYZError) as excinfo:
xyz_reader.read()
assert str(excinfo.value) == "Invalid magic bytes: b'ABC'"
But that test will fail now, because we do not have XYZError defined:
>>> test_when_invalid_magic_bytes_received__then_xyz_error_is_raised()
Traceback (most recent call last):
...
NameError: name 'XYZError' is not defined
So let’s define it:
class XYZError(Exception):
pass
And if now run the test again, it will fail with
mockify.exc.OversaturatedCall
error, because we do not have that
functionality implemented yet:
>>> test_when_invalid_magic_bytes_received__then_xyz_error_is_raised()
Traceback (most recent call last):
...
mockify.exc.OversaturatedCall: Following expectation was oversaturated:
at <doctest default[0]>:8
-------------------------
Pattern:
stream_reader.readline()
Expected:
to be called once
Actual:
oversaturated by stream_reader.readline() at <doctest default[0]>:8 (no more actions)
Now we need to go back to our XYZReader class and fix it by implementing exception raising when invalid magic bytes are received:
class XYZError(Exception):
pass
class XYZReader:
def __init__(self, stream_reader):
self._stream_reader = stream_reader
def read(self):
magic_bytes = self._stream_reader.readline()
magic_bytes = magic_bytes.rstrip()
if magic_bytes != b'XYZ':
raise XYZError("Invalid magic bytes: {!r}".format(magic_bytes))
self._stream_reader.readline()
payload_size = self._stream_reader.readline()
payload_size = payload_size.rstrip()
payload_size = int(payload_size)
return self._stream_reader.read(payload_size)
And now our second test will run fine, but first one will fail:
>>> test_read_xyz_message()
Traceback (most recent call last):
...
AttributeError: 'NoneType' object has no attribute 'rstrip'
Let’s have a look at our first test again:
from mockify.core import satisfied
from mockify.mock import Mock
from mockify.actions import Return
def test_read_xyz_message():
stream_reader = Mock('stream_reader')
stream_reader.readline.expect_call().times(2) # (1)
stream_reader.readline.expect_call().will_once(Return(b'12\n'))
stream_reader.read.expect_call(12).will_once(Return(b'Hello world!'))
xyz_reader = XYZReader(stream_reader)
with satisfied(stream_reader):
assert xyz_reader.read() == b'Hello world!'
As you can see, at (1) we are expecting readline() to be called twice, but we did not provided any value to be returned. And that was fine when we were implementing OK case, but since we have changed XYZReader class, we need to inject proper magic bytes here. Here’s fixed OK case test:
from mockify.core import satisfied
from mockify.mock import Mock
from mockify.actions import Return
def test_read_xyz_message():
stream_reader = Mock('stream_reader')
stream_reader.readline.expect_call().will_once(Return(b'XYZ\n'))
stream_reader.readline.expect_call()
stream_reader.readline.expect_call().will_once(Return(b'12\n'))
stream_reader.read.expect_call(12).will_once(Return(b'Hello world!'))
xyz_reader = XYZReader(stream_reader)
with satisfied(stream_reader):
assert xyz_reader.read() == b'Hello world!'
Verifying version¶
Since third of our tests will be basically written in the same way as second one, let me just present final solution.
Here’s XYZReader class with code that verifies version:
class XYZError(Exception):
pass
class XYZReader:
def __init__(self, stream_reader):
self._stream_reader = stream_reader
def read(self):
magic_bytes = self._stream_reader.readline()
magic_bytes = magic_bytes.rstrip()
if magic_bytes != b'XYZ':
raise XYZError("Invalid magic bytes: {!r}".format(magic_bytes))
version = self._stream_reader.readline()
version = version.rstrip()
if version != b'1.0':
raise XYZError("Unsupported version: {!r}".format(version))
payload_size = self._stream_reader.readline()
payload_size = payload_size.rstrip()
payload_size = int(payload_size)
return self._stream_reader.read(payload_size)
And here’s our third test - the one that checks if exception is raised when invalid version is provided:
import pytest
from mockify.mock import Mock
from mockify.actions import Return
def test_when_invalid_version_received__then_xyz_error_is_raised():
stream_reader = Mock('stream_reader')
stream_reader.readline.expect_call().will_once(Return(b'XYZ\n')) # (1)
stream_reader.readline.expect_call().will_once(Return(b'2.0\n')) # (2)
xyz_reader = XYZReader(stream_reader)
with pytest.raises(XYZError) as excinfo:
xyz_reader.read()
assert str(excinfo.value) == "Unsupported version: b'2.0'" # (3)
Here we have two readline() expectations recorded. At (1) we’ve set valid magic bytes (we are not interested in exception raised at that point), and then at (2) we’ve set an unsupported version, causing XYZError to be raised. Finally, at (3) we are checking if valid exception was raised.
Of course we also had to fix our first test again, returning valid version
instead of None
:
from mockify.core import satisfied
from mockify.mock import Mock
from mockify.actions import Return
def test_read_xyz_message():
stream_reader = Mock('stream_reader')
stream_reader.readline.expect_call().will_once(Return(b'XYZ\n'))
stream_reader.readline.expect_call().will_once(Return(b'1.0\n'))
stream_reader.readline.expect_call().will_once(Return(b'12\n'))
stream_reader.read.expect_call(12).will_once(Return(b'Hello world!'))
xyz_reader = XYZReader(stream_reader)
with satisfied(stream_reader):
assert xyz_reader.read() == b'Hello world!'
Refactoring tests¶
If you take a look at all three tests at once you’ll see a some parts are basically copied and pasted. Creating stream_reader mock, instantiating XYZReader class and checking if mocks are satisfied can be done better if we use organize our tests with a class:
import pytest
from mockify.core import assert_satisfied
from mockify.mock import Mock
from mockify.actions import Return
class TestXYZReader:
def setup_method(self):
self.stream_reader = Mock('stream_reader') # (1)
self.uut = XYZReader(self.stream_reader) # (2)
def teardown_method(self):
assert_satisfied(self.stream_reader) # (3)
def test_read_xyz_message(self):
self.stream_reader.readline.expect_call().will_once(Return(b'XYZ\n'))
self.stream_reader.readline.expect_call().will_once(Return(b'1.0\n'))
self.stream_reader.readline.expect_call().will_once(Return(b'12\n'))
self.stream_reader.read.expect_call(12).will_once(Return(b'Hello world!'))
assert self.uut.read() == b'Hello world!'
def test_when_invalid_magic_bytes_received__then_xyz_error_is_raised(self):
self.stream_reader.readline.expect_call().will_once(Return(b'ABC\n'))
with pytest.raises(XYZError) as excinfo:
self.uut.read()
assert str(excinfo.value) == "Invalid magic bytes: b'ABC'"
def test_when_invalid_version_received__then_xyz_error_is_raised(self):
self.stream_reader.readline.expect_call().will_once(Return(b'XYZ\n'))
self.stream_reader.readline.expect_call().will_once(Return(b'2.0\n'))
with pytest.raises(XYZError) as excinfo:
self.uut.read()
assert str(excinfo.value) == "Unsupported version: b'2.0'"
Tip
Alternatively you can use fixtures instead of setup_method() and teardown_method(). Fixtures are way more powerful. For more details please visit https://docs.pytest.org/en/latest/fixture.html.
We’ve moved mock (1) and unit under test (2) construction into
setup_method() method and used mockify.core.assert_satisfied()
function
(3) in teardown_method(). That function works the same as
mockify.core.satisfied()
, but is not a context manager. Notice that we’ve
also removed context manager from OK test, as it is no longer needed.
Now, once tests are refactored, you can just add another tests without even remembering to check the mock before test is done - it all happens automatically. And the tests look much cleaner than before refactoring. There is even more: you can easily extract recording expectations to separate methods if needed.
Putting it all together¶
Here’s once again complete XYZReader class:
class XYZError(Exception):
pass
class XYZReader:
def __init__(self, stream_reader):
self._stream_reader = stream_reader
def read(self):
magic_bytes = self._stream_reader.readline()
magic_bytes = magic_bytes.rstrip()
if magic_bytes != b'XYZ':
raise XYZError("Invalid magic bytes: {!r}".format(magic_bytes))
version = self._stream_reader.readline()
version = version.rstrip()
if version != b'1.0':
raise XYZError("Unsupported version: {!r}".format(version))
payload_size = self._stream_reader.readline()
payload_size = payload_size.rstrip()
payload_size = int(payload_size)
return self._stream_reader.read(payload_size)
And tests:
import pytest
from mockify.core import assert_satisfied
from mockify.mock import Mock
from mockify.actions import Return
class TestXYZReader:
def setup_method(self):
self.stream_reader = Mock('stream_reader') # (1)
self.uut = XYZReader(self.stream_reader) # (2)
def teardown_method(self):
assert_satisfied(self.stream_reader) # (3)
def test_read_xyz_message(self):
self.stream_reader.readline.expect_call().will_once(Return(b'XYZ\n'))
self.stream_reader.readline.expect_call().will_once(Return(b'1.0\n'))
self.stream_reader.readline.expect_call().will_once(Return(b'12\n'))
self.stream_reader.read.expect_call(12).will_once(Return(b'Hello world!'))
assert self.uut.read() == b'Hello world!'
def test_when_invalid_magic_bytes_received__then_xyz_error_is_raised(self):
self.stream_reader.readline.expect_call().will_once(Return(b'ABC\n'))
with pytest.raises(XYZError) as excinfo:
self.uut.read()
assert str(excinfo.value) == "Invalid magic bytes: b'ABC'"
def test_when_invalid_version_received__then_xyz_error_is_raised(self):
self.stream_reader.readline.expect_call().will_once(Return(b'XYZ\n'))
self.stream_reader.readline.expect_call().will_once(Return(b'2.0\n'))
with pytest.raises(XYZError) as excinfo:
self.uut.read()
assert str(excinfo.value) == "Unsupported version: b'2.0'"
And that’s the end of quickstart guide :-)
Now you can proceed to Tutorial section, covering some more advanced features, or just try it out in your projects. Thanks for reaching that far. I hope you will find Mockify useful.
Tutorial¶
Creating mocks and recording expectations¶
Introduction¶
Since version 0.6 Mockify provides single mockify.mock.Mock
class
for mocking things. With that class you will be able to mock:
- functions,
- objects with methods,
- modules with functions,
- setters and getters.
That new class can create attributes when you first access them and then you can record expectations on that attributes. Furthermore, that attributes are callable. When you call one, it consumes previously recorded expectations.
To create a mock, you need to import mockify.mock.Mock
class and
instantiate it with a name of choice:
from mockify.mock import Mock
foo = Mock('foo')
That name should reflect what is being mocked and this should be function, object or module name. You can only use names that are valid Python identifiers or valid Python module names, with submodules separated with a period sign.
Now let’s take a brief introduction to what can be done with just created foo object.
Mocking functions¶
Previously created foo mock can be used to mock a function or any other callable. Consider this example code:
def async_sum(a, b, callback):
result = a + b
callback(result)
We have “asynchronous” function that calculates sum of a and b and triggers given callback with a sum of those two. Now, let’s call that function with foo mock object as a callback. This will happen:
>>> async_sum(2, 3, foo)
Traceback (most recent call last):
...
mockify.exc.UninterestedCall: No expectations recorded for mock:
at <doctest default[0]>:3
-------------------------
Called:
foo(5)
Now you should notice two things:
- Mock object foo is callable and was called with 5 (2 + 3 = 5),
- Exception
mockify.exc.UninterestedCall
was raised, caused by lack of expectations on mock foo.
Raising that exception is a default behavior of Mockify. You can change this
default behavior (see mockify.Session.config
for more details), but
it can be very useful, because it will make your tests fail early and you
will see what expectation needs to be recorded to move forward. In our case
we need to record foo(5) call expectation.
To do this you will need to call expect_call() method on foo object:
foo.expect_call(5)
Calling expect_call() records call expectation on rightmost mock attribute, which is foo in this case. Given arguments must match the arguments the mock will later be called with.
And if you call async_sum again, it will now pass:
from mockify.core import satisfied
with satisfied(foo):
async_sum(2, 3, foo)
Note that we’ve additionally used mockify.core.satisfied()
. It’s a context
manager for wrapping portions of test code that satisfies one or more
given mocks. And mock is satisfied if all expectations recorded for it are
satisfied, meaning that they were called exactly expected number of
times. Alternatively, you could also use mockify.core.assert_satisfied()
function:
from mockify.core import assert_satisfied
foo.expect_call(3)
async_sum(1, 2, foo)
assert_satisfied(foo)
That actually work in the same way as context manager version, but can be used out of any context, for example in some kind of teardown function.
Mocking objects with methods¶
Now let’s take a look at following code:
class APIGateway:
def __init__(self, connection):
self._connection = connection
def list_users(self):
return self._connection.get('/api/users')
This class implements a facade on some lower level connection object. Let’s now create instance of APIGateway class. Oh, it cannot be created without a connection argument… That’s not a problem - let’s use a mock for that:
connection = Mock('connection')
gateway = APIGateway(connection)
If you now call APIGateway.list_users() method, you will see similar error to the one we had earlier:
>>> gateway.list_users()
Traceback (most recent call last):
...
mockify.exc.UninterestedCall: No expectations recorded for mock:
at <doctest default[0]>:7
-------------------------
Called:
connection.get('/api/users')
And again, we need to record matching expectation to move test forward. To record method call expectation you basically need to do the same as for functions, but with additional attribute - a method object:
connection.get.expect_call('/api/users')
with satisfied(connection):
gateway.list_users()
And now it works fine.
Mocking functions behind a namespace or module¶
This kind of mocking is extended version of previous one.
Now consider this example:
class APIGateway:
def __init__(self, connection):
self._connection = connection
def list_users(self):
return self._connection.http.get('/api/users')
We have basically the same example, but this time our connection interface was divided between various protocols. You can assume that connection object handles entire communication with external world by providing a facade to lower level libs. And http part is one of them.
To mock that kind of stuff you basically only need to add another attribute to connection mock, and call expect_call() on that attribute. Here’s a complete example:
connection = Mock('connection')
gateway = APIGateway(connection)
connection.http.get.expect_call('/api/users')
with satisfied(connection):
gateway.list_users()
Creating ad-hoc data objects¶
Class mockify.mock.Mock
can also be used to create ad-hoc data
objects to be used as a response for example. To create one, you just need to
instantiate it, and assign values to automatically created properties. Like
in this example:
mock = Mock('mock')
mock.foo = 1
mock.bar = 2
mock.baz.spam.more_spam = 'more spam' # (1)
The most cool feature about data objects created this way is (1) - you can assign values to any nested attributes. And now let’s get those values:
>>> mock.foo
1
>>> mock.bar
2
>>> mock.baz.spam.more_spam
'more spam'
Mocking getters¶
Let’s take a look at following function:
def unpack(obj, *names):
for name in names:
yield getattr(obj, name)
That function yields attributes extracted from given obj in order specified by names. Of course it is a trivial example, but we’ll use a mock in place of obj and will record expectations on property getting. And here’s the solution:
from mockify.actions import Return
obj = Mock('obj')
obj.__getattr__.expect_call('a').will_once(Return(1)) # (1)
obj.__getattr__.expect_call('b').will_once(Return(2)) # (2)
with satisfied(obj):
assert list(unpack(obj, 'a', 'b')) == [1, 2] # (3)
As you can see, recording expectation of getting property on (1) and (2) is that you record call expectation on a magic method __getattr__. And similar to data objects, you can record getting attribute expectation at any nesting level - just prefix expect_call() with __getattr__ attribute and you’re done.
Mocking setters¶
Just like getters, setters can also be mocked with Mockify. The difference is that you will have to use __setattr__.expect_call() this time and obligatory give two arguments:
- attribute name,
- and value you expect it to be set with.
Here’s a complete solution with a pack function - a reverse of the one used in previous example:
def pack(obj, **kwargs):
for name, value in kwargs.items():
setattr(obj, name, value)
obj = Mock('obj')
obj.__setattr__.expect_call('a', 1)
obj.__setattr__.expect_call('b', 2)
with satisfied(obj):
pack(obj, a=1, b=2)
And that also work on nested attributes.
Most common assertions¶
Uninterested mock calls¶
Just after mock object is created, it does not have any expectations
recorded. Calling a mock with no expectations is by default not possible and
results in mockify.exc.UninterestedCall
assertion and test
termination:
>>> from mockify.mock import Mock
>>> mock = Mock('mock')
>>> mock()
Traceback (most recent call last):
...
mockify.exc.UninterestedCall: No expectations recorded for mock:
at <doctest default[2]>:1
-------------------------
Called:
mock()
That error will be raised for any attribute you would call on such mock with no expectations. That default behavior can be changed (see Using sessions for more details), but it can get really useful. For example, if you are writing tests for existing code, you could write tests in step-by-step mode, recording expectations one-by-one.
Unexpected mock calls¶
This new behavior was introduced in version 0.6.
It is meant to differentiate mocks that has no expectations from mocks that have at least one, but not matching actual call. This is illustrated by following example:
from mockify.mock import Mock
mock = Mock('mock')
mock.expect_call(1, 2)
We have mock mock that is expected to be called with 1 and 2 as two
positional arguments. And now, if that mock is called with unexpected
parameters, for instance 1 and 3, mockify.exc.UnexpectedCall
assertion will be raised:
>>> mock(1, 3)
Traceback (most recent call last):
...
mockify.exc.UnexpectedCall: No matching expectations found for call:
at <doctest default[0]>:1
-------------------------
Called:
mock(1, 3)
Expected (any of):
mock(1, 2)
That error is basically extended version of previous uninterested call error, with additional list of existing expectations. That will make it easier to decide if expectation has a typo, or if there is a bug in tested code.
Unsatisfied and satisfied mocks¶
All previously presented assertion errors can only be raised during mock call. But even if mock is called with expected parameters and for each call matching expectation is found, we still need a way to verify if expectations we’ve recorded are satisfied, which means that all are called expected number of times.
To check if mock is satisfied you can use mockify.core.assert_satisfied()
function. This function can be used more than once, but usually the best
place to check if mock is satisfied is at the end of test function.
Each newly created mock is already satisfied:
from mockify.core import assert_satisfied
from mockify.mock import Mock
foo = Mock('foo')
assert_satisfied(foo)
Let’s now record some expectation:
foo.bar.expect_call('spam')
When expectation is recorded, then mock becomes unsatisfied, which means
that it is not yet or not fully consumed. That will be reported with
mockify.exc.Unsatisfied
assertion:
>>> assert_satisfied(foo)
Traceback (most recent call last):
...
mockify.exc.Unsatisfied: Following expectation is not satisfied:
at <doctest default[0]>:1
-------------------------
Pattern:
foo.bar('spam')
Expected:
to be called once
Actual:
never called
The exception will print out all unsatisfied expectations with their:
- location in test code,
- call pattern that describes function or method with its parameters,
- expected call count of the pattern,
- and actual call count.
By reading exception we see that our method is expected to be called once and was never called. That’s true, because we’ve only recorded an expectation so far. To make foo satisfied again we need to call the method with params that will match the expectation:
from mockify.core import satisfied
with satisfied(foo):
foo.bar('spam')
In example above we’ve used mockify.core.satisfied()
context manager instead
of mockify.core.assert_satisfied()
presented above. Those two work in
exactly the same way, raising exactly the same exceptions, but context
manager version is better suited for simple tests or when you want to mark
part of test code that satisfies all given mocks.
If you now call our expected method again, the call will not raise any exceptions:
foo.bar('spam')
And even if you run it 5 more times, it will still just work:
for _ in range(5):
foo.bar('spam')
But the mock will no longer be satisfied even after first of that additional calls:
>>> assert_satisfied(foo)
Traceback (most recent call last):
...
mockify.exc.Unsatisfied: Following expectation is not satisfied:
at <doctest default[0]>:1
-------------------------
Pattern:
foo.bar('spam')
Expected:
to be called once
Actual:
called 7 times
So once again, we have mockify.exc.Unsatisfied
raised. But as you can
see, the mock was called 7 times so far, while it still is expected to be
called exactly once.
Why there was no exception raised on second call?
Well, this was made like this actually to make life easier. Mockify allows
you to record very sophisticated expectations, including expected call count
ranges etc. And when mock is called it does not know how many times it will be
called during the test, so we must explicitly tell it that testing is done.
And that’s why mockify.core.assert_satisfied()
is needed. Moreover, it is
the only single assertion function you will find in Mockify (not counting its
context manager counterpart).
Setting expected call count¶
Expecting mock to be called given number of times¶
When you create expectation, you implicitly expect your mock to be called exactly once with given given params:
from mockify.core import satisfied
from mockify.mock import Mock
foo = Mock('foo')
foo.expect_call(1, 2)
with satisfied(foo):
foo(1, 2)
But what if we need our mock to be called exactly N-times?
First solution is simply to repeat expectation exactly N-times. And
here’s example test that follows this approach, expecting foo
mock to be
called exactly three times:
from mockify.core import satisfied
from mockify.mock import Mock
def func_caller(func, a, b, count):
for _ in range(count):
func(a, b)
def test_func_caller():
foo = Mock('foo')
for _ in range(3):
foo.expect_call(1, 2)
with satisfied(foo):
func_caller(foo, 1, 2, 3)
Although that will certainly work, it is not the best option, as it unnecessary complicates test code. So here’s another example, presenting recommended solution for setting expected call count to fixed value:
def test_func_caller():
foo = Mock('foo')
foo.expect_call(1, 2).times(3) # (1)
with satisfied(foo):
func_caller(foo, 1, 2, 3)
We’ve removed loop from test function and instead used
mockify.core.Expectation.times()
method (1), giving it expected number of
calls to foo(1, 2)
. Thanks to this, our expectation is self-explanatory
and in case of unsatisfied assertion you will see that expected call count in
error message:
>>> from mockify.core import assert_satisfied
>>> from mockify.mock import Mock
>>> foo = Mock('foo')
>>> foo.expect_call().times(3)
<mockify.core.Expectation: foo()>
>>> assert_satisfied(foo)
Traceback (most recent call last):
...
mockify.exc.Unsatisfied: Following expectation is not satisfied:
at <doctest default[3]>:1
-------------------------
Pattern:
foo()
Expected:
to be called 3 times
Actual:
never called
Expecting mock to be never called¶
Although expecting something to never happen is a bit tricky, here we can use
it to overcome mockify.exc.UninterestedCall
and
mockify.exc.UnexpectedCall
assertions. Normally, if mock is called
with parameters for which there are no matching expectations, the call will
fail with one of mentioned exceptions. But you can change that to
mockify.exc.Unsatisfied
assertion with following simple trick:
from mockify.core import assert_satisfied
from mockify.mock import Mock
foo = Mock('foo')
foo.expect_call(-1).times(0) # (1) #
assert_satisfied(foo)
As you can see, the mock is satisfied despite the fact it does have
an expectation recorded at (1). But that expectation has expected call count
set to zero with times(0)
call. And that’s the trick - you are explicitly
expecting foo to be never called (or called zero times) with -1 as
an argument.
And now if you make a matching call, the mock will instantly become unsatisfied:
>>> foo(-1)
>>> assert_satisfied(foo)
Traceback (most recent call last):
...
mockify.exc.Unsatisfied: Following expectation is not satisfied:
at <doctest default[0]>:5
-------------------------
Pattern:
foo(-1)
Expected:
to be never called
Actual:
called once
And that’s the whole trick.
Setting expected call count using cardinality objects¶
Previously presented mockify.core.Expectation.times()
can also be used in
conjunction with so called cardinality objects available via
mockify.cardinality
module.
Here’s an example of setting minimal expected call count:
from mockify.mock import Mock
from mockify.cardinality import AtLeast
foo = Mock('foo')
foo.expect_call().times(AtLeast(1)) # (1)
In example above we’ve recorded expectation that foo()
will be called
at least once by passing mockify.cardinality.AtLeast
instance to
times()
method. So currently it will not be satisfied, because it is not
called yet:
>>> from mockify.core import assert_satisfied
>>> assert_satisfied(foo)
Traceback (most recent call last):
...
mockify.exc.Unsatisfied: Following expectation is not satisfied:
at <doctest default[0]>:5
-------------------------
Pattern:
foo()
Expected:
to be called at least once
Actual:
never called
But after it is called and made satisfied:
>>> foo()
>>> assert_satisfied(foo)
It will be satisfied forever - no matter how many times foo()
will be
called afterwards:
>>> for _ in range(10):
... foo()
>>> assert_satisfied(foo)
Using the same approach you can also set:
- maximal call count (
mockify.cardinality.AtMost
), - or ranged call count (
mockify.cardinality.Between
).
Recording actions¶
What are actions used for?¶
In Mockify, each mocked function by default returns None
when called with
parameters for which expectation was recorded:
from mockify.mock import Mock
foo = Mock('foo')
foo.expect_call(1)
foo.expect_call(2)
assert foo(1) is None
assert foo(2) is None
That behavior is a normal thing for mocking command-like functions, i.e.
functions that do something by modifying internal state of an object,
signalling only failures with various exceptions. That functions does not
need or even must not return any values, and in Python function that does not
return anything implicitly returns None
.
But there are also query-like methods and that kind of methods must return a value of some kind, as we use them to obtain current state of some object.
So we basically have two problems to solve:
- How to force mocks of command-like functions to raise exceptions?
- How to force mocks of query-like functions to return various values, but
different than
None
?
That’s where actions come in. In Mockify you have various actions
available via mockify.actions
module. With actions you can, in
addition to recording expectations, set what the mock will do when called
with matching set of parameters. For example, you can:
- set value to be returned by mock (
mockify.actions.Return
), - set exception to be raised by mock (
mockify.actions.Raise
), - set function to be called by mock (
mockify.actions.Invoke
), - or run your custom action (defined by subclassing
mockify.actions.Action
class).
Single actions¶
To record single action, you have to use
mockify.core.Expectation.will_once()
method and give it instance of action
you want your mock to perform. For example:
from mockify.mock import Mock
from mockify.actions import Return
foo = Mock('foo')
foo.expect_call(1).will_once(Return('one')) # (1)
foo.expect_call(2).will_once(Return('two')) # (2)
We’ve recorded two expectations, and set a return value for each. Actions are
tied together with expectations, so in our example we’ve recorded that
foo(1)
will return 'one'
(1), and that foo(2)
will return
'two'
.
Mocks with actions set are not satisfied if there are actions left to be
consumed. If we now check if foo is satisfied,
mockify.exc.Unsatisfied
will be raised with two unsatisfied
expectations defined in (1) and (2) present:
>>> from mockify.core import assert_satisfied
>>> assert_satisfied(foo)
Traceback (most recent call last):
...
mockify.exc.Unsatisfied: Following 2 expectations are not satisfied:
at <doctest default[0]>:5
-------------------------
Pattern:
foo(1)
Action:
Return('one')
Expected:
to be called once
Actual:
never called
at <doctest default[0]>:6
-------------------------
Pattern:
foo(2)
Action:
Return('two')
Expected:
to be called once
Actual:
never called
Notice that the exception also shows an action to be performed next. That information is not present if you have no custom actions recorded. Let’s now call foo with params matching previously recorded expectations:
>>> foo(1)
'one'
>>> foo(2)
'two'
>>> assert_satisfied(foo)
As you can see, the mock returned values we’ve recorded. And it is also satisfied now.
Action chains¶
It is also possible to record multiple actions on single expectation,
simply by adding more mockify.core.Expectation.will_once()
method calls:
from mockify.mock import Mock
from mockify.actions import Return
count = Mock('count')
count.expect_call().\
will_once(Return(1)).\
will_once(Return(2)).\
will_once(Return(3))
In example above we’ve created a mock named count, and it will consume and invoke subsequent action on each call:
>>> count()
1
>>> count()
2
>>> count()
3
That’s how action chains work. Of course each chain is tied to a particular expectation, so you are able to create different chains for different expectations. And you can have different actions in your chains, and even mix them.
When multiple single actions are recorded, then mock is implicitly expected
to be called N-times, where N is a number of actions in a chain. But if you
have actions recorded and mock gets called more times than expected, it will
fail on mock call with mockify.exc.OversaturatedCall
:
>>> count()
Traceback (most recent call last):
...
mockify.exc.OversaturatedCall: Following expectation was oversaturated:
at <doctest default[0]>:5
-------------------------
Pattern:
count()
Expected:
to be called 3 times
Actual:
oversaturated by count() at <doctest default[0]>:1 (no more actions)
That error will only be raised if you are using actions. Normally, the mock would simply be unsatisfied. But it was added for a reason; if there are no more custom actions recorded and mock is called again, then it would most likely fail few lines later (f.e. due to invalid value type), but with stacktrace pointing to tested code, not to call of mocked function. And that would potentially be harder to debug.
Repeated actions¶
You also can record so called repeated actions with
mockify.core.Expectation.will_repeatedly()
method instead of previously used
will_once()
:
from mockify.mock import Mock
from mockify.actions import Return
foo = Mock('foo')
foo.expect_call().will_repeatedly(Return(123)) # (1)
Repeated actions defined like in (1) can be executed any number of times, including zero, so currently mock foo is already satisfied:
>>> assert_satisfied(foo)
Repeated actions are useful when you need same thing to be done every single
time the mock is called. So if foo()
is now called, it will always return
123
, as we’ve declared in (1):
>>> [foo() for _ in range(4)]
[123, 123, 123, 123]
And foo will always be satisfied:
>>> assert_satisfied(foo)
Repeated actions with cardinality¶
You can also declare repeated actions that can only be executed given number
of times by simply adding call to mockify.core.Expectation.times()
method
just after will_repeatedly()
:
from mockify.mock import Mock
from mockify.actions import Return
foo = Mock('foo')
foo.expect_call().will_repeatedly(Return(123)).times(1) # (1)
Such declared expectation will have to be executed exactly once. But of
course you can use any cardinality object from mockify.cardinality
to
record even more complex behaviors. The difference between such constrained
repeated actions and actions recorded using will_once()
is that repeated
actions cannot be oversaturated - the mock will simply keep returning value
we’ve set, but of course will no longer be satisfied:
>>> foo()
123
>>> assert_satisfied(foo)
>>> foo()
123
>>> assert_satisfied(foo)
Traceback (most recent call last):
...
mockify.exc.Unsatisfied: Following expectation is not satisfied:
at <doctest default[0]>:5
-------------------------
Pattern:
foo()
Action:
Return(123)
Expected:
to be called once
Actual:
called twice
Using chained and repeated actions together¶
It is also possible to use both single and repeated actions together, like in this example:
from mockify.mock import Mock
from mockify.actions import Return
foo = Mock('foo')
foo.expect_call().\
will_once(Return(1)).\
will_once(Return(2)).\
will_repeatedly(Return(3))
Such declared expectations have implicitly set minimal expected call
count that is equal to number of actions recorded using will_once()
. So
currently the mock is not satisfied:
>>> assert_satisfied(foo)
Traceback (most recent call last):
...
mockify.exc.Unsatisfied: Following expectation is not satisfied:
at <doctest default[0]>:5
-------------------------
Pattern:
foo()
Action:
Return(1)
Expected:
to be called at least twice
Actual:
never called
But the mock becomes satisfied after it is called twice:
>>> foo()
1
>>> foo()
2
>>> assert_satisfied(foo)
And at this point it will continue to be satisfied - no matter how many times it is called after. And for every call it will execute previously set repeated action:
>>> foo()
3
>>> foo()
3
>>> assert_satisfied(foo)
Using chained and repeated actions with cardinality¶
You can also record expectations like this one:
from mockify.mock import Mock
from mockify.actions import Return
foo = Mock('foo')
foo.expect_call().\
will_once(Return(1)).\
will_once(Return(2)).\
will_repeatedly(Return(3)).\
times(2) # (1)
Basically, this is a constrained version of previous example in which repeated action is expected to be called only twice. But total expected call count is 4, as we have two single actions recorded:
>>> assert_satisfied(foo)
Traceback (most recent call last):
...
mockify.exc.Unsatisfied: Following expectation is not satisfied:
at <doctest default[0]>:5
-------------------------
Pattern:
foo()
Action:
Return(1)
Expected:
to be called 4 times
Actual:
never called
Now let’s satisfy the expectation by calling a mock:
>>> [foo() for _ in range(4)]
[1, 2, 3, 3]
>>> assert_satisfied(foo)
Since last of your actions is a repeated action, you can keep calling the mock more times:
>>> foo()
3
But the mock will no longer be satisfied, as we’ve recorded at (1) that repeated action will be called exactly twice:
>>> assert_satisfied(foo)
Traceback (most recent call last):
...
mockify.exc.Unsatisfied: Following expectation is not satisfied:
at <doctest default[0]>:5
-------------------------
Pattern:
foo()
Action:
Return(3)
Expected:
to be called 4 times
Actual:
called 5 times
Managing multiple mocks¶
Introduction¶
So far we’ve discussed situations where single mock object is suitable and fits well. But those are very rare situations, as usually you will need more than just one mock. Let’s now take a look at following Python code:
import hashlib
import base64
class AlreadyRegistered(Exception):
pass
class RegisterUserAction:
def __init__(self, database, crypto, mailer):
self._database = database
self._crypto = crypto
self._mailer = mailer
def invoke(self, email, password):
session = self._database.session()
if session.users.exists(email):
raise AlreadyRegistered("E-mail {!r} is already registered".format(email))
password = self._crypto.hash_password(password)
session.users.add(email, password)
self._mailer.send_confirm_registration_to(email)
session.commit()
That classes implements business logic of user registration process:
- User begins registration by entering his/her e-mail and password,
- System verifies whether given e-mail is already registered,
- System adds new user to users database and marks as “confirmation in progress”,
- System sends confirmation email to the User with confirmation link.
That use case has dependencies to database, e-mail sending service and service that provides some sophisticated way of generating random numbers suitable for cryptographic use. Now let’s write one test for that class:
from mockify.core import satisfied
from mockify.mock import Mock
from mockify.actions import Return
def test_register_user_action():
session = Mock('session') # (1)
database = Mock('database')
crypto = Mock('crypto')
mailer = Mock('mailer')
database.session.\
expect_call().will_once(Return(session))
session.users.exists.\
expect_call('foo@bar.com').will_once(Return(False))
crypto.hash_password.\
expect_call('p@55w0rd').will_once(Return('***'))
session.users.add.\
expect_call('foo@bar.com', '***')
mailer.send_confirm_registration_to.\
expect_call('foo@bar.com')
session.commit.\
expect_call()
action = RegisterUserAction(database, crypto, mailer)
with satisfied(database, session, crypto, mailer): # (2)
action.invoke('foo@bar.com', 'p@55w0rd')
We had to create 3 mocks + one additional at (1) for mocking database session. And since we have 4 mock objects, we also need to remember to verify them all at (2). And remembering things may lead to bugs in the test code. But Mockify is supplied with tools that will help you deal with that.
Using mock factories¶
First solution is to use mockify.mock.MockFactory
class. With that
class you will be able to create mocks without need to use
mockify.mock.Mock
directly. Morever, mock factories will not allow
you to duplicate mock names and will automatically track all created mocks
for you. Besides, all mocks created by one factory will share same
session object and that is important for some of Mockify’s features.
Here’s our previous test rewritten to use mock factory instead of several mock objects:
from mockify.core import satisfied
from mockify.mock import MockFactory
from mockify.actions import Return
def test_register_user_action():
factory = MockFactory() # (1)
session = factory.mock('session')
database = factory.mock('database')
crypto = factory.mock('crypto')
mailer = factory.mock('mailer')
database.session.\
expect_call().will_once(Return(session))
session.users.exists.\
expect_call('foo@bar.com').will_once(Return(False))
crypto.hash_password.\
expect_call('p@55w0rd').will_once(Return('***'))
session.users.add.\
expect_call('foo@bar.com', '***')
mailer.send_confirm_registration_to.\
expect_call('foo@bar.com')
session.commit.\
expect_call()
action = RegisterUserAction(database, crypto, mailer)
with satisfied(factory): # (2)
action.invoke('foo@bar.com', 'p@55w0rd')
Although the code did not change a lot in comparison to previous version, we’ve introduced a major improvement. At (1) we’ve created a mock factory instance, which is used to create all needed mocks. Also notice, that right now we only check factory object at (2), so we don’t have to remember all the mocks we’ve created. That saves a lot of problems later, when test is modified; each new mock will most likely be created using factory object and it will automatically check that new mock.
Using mock factories with test suites¶
Mock factories work the best with test suites containing setup and teardown customizable steps executed before and after every single test. Here’s once again our test, but this time in form of test suite (written as an example, without use of any specific framework):
from mockify.core import assert_satisfied
from mockify.mock import MockFactory
from mockify.actions import Return
class TestRegisterUserAction:
def setup(self):
self.factory = MockFactory() # (1)
self.session = self.factory.mock('session') # (2)
self.database = self.factory.mock('database')
self.crypto = self.factory.mock('crypto')
self.mailer = self.factory.mock('mailer')
self.database.session.\
expect_call().will_repeatedly(Return(self.session)) # (3)
self.uut = RegisterUserAction(self.database, self.crypto, self.mailer) # (4)
def teardown(self):
assert_satisfied(self.factory) # (5)
def test_register_user_action(self):
self.session.users.exists.\
expect_call('foo@bar.com').will_once(Return(False))
self.crypto.hash_password.\
expect_call('p@55w0rd').will_once(Return('***'))
self.session.users.add.\
expect_call('foo@bar.com', '***')
self.mailer.send_confirm_registration_to.\
expect_call('foo@bar.com')
self.session.commit.\
expect_call()
self.uut.invoke('foo@bar.com', 'p@55w0rd')
Notice, that we’ve moved factory to setup()
method (1), and created all
mocks inside it (2) along with unit under test instance (4). Also notice that
obtaining database session (3) was also moved to setup step and made optional
with will_repeatedly()
. Finally, our factory (and every single mock
created by it) is verified at (5), during teardown phase of test execution.
Thanks to that we have only use case specific expectations in test method,
and a common setup code, so it is now much easier to add more tests to that
class.
Note
If you are using pytest, you can take advantage of fixtures and use those instead of setup/teardown methods:
import pytest
from mockify.core import satisfied
from mockify.mock import MockFactory
@pytest.fixture
def mock_factory():
factory = MockFactory()
with satisfied(factory):
yield factory
def test_something(mock_factory):
mock = mock_factory.mock('mock')
# ...
Using sessions¶
A core part of Mockify library is a session. Sessions are instances of
mockify.core.Session
class and their role is to provide mechanism for
storing recorded expectations, and matching them with calls being made.
Normally sessions are created automatically by each mock or mock factory, but
you can also give it explicitly via session argument:
from mockify.core import Session
from mockify.mock import Mock, MockFactory
session = Session() # (1)
first = Mock('first', session=session) # (2)
second = MockFactory(session=session) # (3)
In example above, we’ve explicity created session object (1) and gave it to mock first (2) and mock factory second (3), which now share the session. This means that all expectations registered for mock first or any of mocks created by factory second will be passed to a common session object. Some of Mockify features, like ordered expectations (see Recording ordered expectations) will require that to work. Although you don’t have to create one common session for all your mocks, creating it explicitly may be needed if you want to:
- override some of Mockify’s default behaviors (see
mockify.Session.config
for more info), - write a common part for your tests.
For the sake of this example let’s stick to the last point. And now, let’s write a base class for our test suite defined before:
from mockify.core import Session
class TestCase:
def setup(self):
self.mock_session = Session() # (1)
def teardown(self):
self.mock_session.assert_satisfied() # (2)
As you can see, nothing really interesting is happening here. We are creating session (1) in setup section and checking it it is satisfied (2) in teardown section. And here comes our test from previous example:
class TestRegisterUserAction(TestCase):
def setup(self):
super().setup()
self.factory = MockFactory(session=self.mock_session) # (1)
self.session = self.factory.mock('session')
self.database = self.factory.mock('database')
self.crypto = self.factory.mock('crypto')
self.mailer = self.factory.mock('mailer')
self.database.session.\
expect_call().will_repeatedly(Return(self.session))
self.uut = RegisterUserAction(self.database, self.crypto, self.mailer)
def test_register_user_action(self):
self.session.users.exists.\
expect_call('foo@bar.com').will_once(Return(False))
self.crypto.hash_password.\
expect_call('p@55w0rd').will_once(Return('***'))
self.session.users.add.\
expect_call('foo@bar.com', '***')
self.mailer.send_confirm_registration_to.\
expect_call('foo@bar.com')
self.session.commit.\
expect_call()
self.uut.invoke('foo@bar.com', 'p@55w0rd')
As you can see, teardown()
method was completely removed because it was
no longer needed - all mocks are checked by one single call to
mockify.core.Session.assert_satisfied()
method in base class. The part that
changed is a setup()
function that triggers base class setup method, and
a mock factory (1) that is given a session. With this approach you only
implement mock checking once - in a base class for your tests. The only thing
you have to remember is to give a session instance to either factory, or each
of your mocks for that to work.
Using matchers¶
Introduction¶
So far we’ve been recording expectations with fixed argument values. But
Mockify provides to you a very powerful mechanism of matchers, available
via mockify.matchers
module. Thanks to the matchers you can record
expectations that will match more than just a single value. Let’s take a
brief tour of what you can do with matchers!
Recording expectations with matchers¶
Let’s take a look at following code that we want to test:
import uuid
class ProductAlreadyExists(Exception):
pass
class AddProductAction:
def __init__(self, database):
self._database = database
def invoke(self, category_id, name, data):
if self._database.products.exists(category_id, name):
raise ProductAlreadyExists()
product_id = str(uuid.uuid4()) # (1)
self._database.products.add(product_id, category_id, name, data) # (2)
That code represents a business logic of adding some kind of product into
database. The product is identified by a name and category, and there
cannot be more than one product of given name inside given category. But
tricky part is at (1), where we calculate UUID for our new product. That
value is random, and we are passing it into products.add()
method, which
will be mocked. How to mock that, when we don’t know what will the value be?
And here comes the matchers:
from mockify.core import satisfied
from mockify.mock import Mock
from mockify.actions import Return
from mockify.matchers import _ # (1)
def test_add_product_action():
database = Mock('database')
database.products.exists.\
expect_call('dummy-category', 'dummy-name').\
will_once(Return(False))
database.products.add.\
expect_call(_, 'dummy-category', 'dummy-name', {'description': 'A dummy product'}) # (2)
action = AddProductAction(database)
with satisfied(database):
action.invoke('dummy-category', 'dummy-name', {'description': 'A dummy product'})
We’ve used a wildcard matcher imported in (1), and placed it as first
argument of our expectation (2). That underscore object is in fact instance
of mockify.matchers.Any
and is basically equal to every possible
Python object, therefore it will match any possible UUID value, so our test
will pass.
Of course you can also use another matcher if you need a more strict check.
For example, we can use mockify.matchers.Regex
to check if this
is a real UUID value:
from mockify.core import satisfied
from mockify.mock import Mock
from mockify.actions import Return
from mockify.matchers import Regex
any_uuid = Regex(r'^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$')
def test_add_product_action():
database = Mock('database')
database.products.exists.\
expect_call('dummy-category', 'dummy-name').\
will_once(Return(False))
database.products.add.\
expect_call(any_uuid, 'dummy-category', 'dummy-name', {'description': 'A dummy product'})
action = AddProductAction(database)
with satisfied(database):
action.invoke('dummy-category', 'dummy-name', {'description': 'A dummy product'})
Combining matchers¶
You can also combine matchers using |
and &
binary operators.
For example, if you want to expect values that can only be integer numbers or
lower case ASCII strings, you can combine mockify.matchers.Type
and
mockify.matchers.Regex
matchers like in this example:
from mockify.mock import Mock
from mockify.actions import Return
from mockify.matchers import Type, Regex
mock = Mock('mock')
mock.\
expect_call(Type(int) | Regex(r'^[a-z]+$', 'LOWER_ASCII')).\
will_repeatedly(Return(True))
And now let’s try it:
>>> mock(1)
True
>>> mock('abc')
True
>>> mock(3.14)
Traceback (most recent call last):
...
mockify.exc.UnexpectedCall: No matching expectations found for call:
at <doctest default[2]>:1
-------------------------
Called:
mock(3.14)
Expected (any of):
mock(Type(int) | Regex(LOWER_ASCII))
In the last line we’ve called our mock with float number which is neither
integer, nor lower ASCII string. And since it did not matched our
expectation, mockify.exc.UnexpectedCall
was raised - the same that
would be raised if we had used fixed values in expectation.
And now let’s try with one more example.
This time we are expecting only positive integer numbers. To expect that we
can combine previously introduced Type
matcher with
mockify.matchers.Func
matcher. The latter is very powerful, as it
accepts any custom function. Here’s our expectation:
from mockify.mock import Mock
from mockify.actions import Return
from mockify.matchers import Type, Func
mock = Mock('mock')
mock.\
expect_call(Type(int) & Func(lambda x: x > 0, 'POSITIVE_ONLY')).\
will_repeatedly(Return(True))
And now let’s do some checks:
>>> mock(1)
True
>>> mock(10)
True
>>> mock(3.14)
Traceback (most recent call last):
...
mockify.exc.UnexpectedCall: No matching expectations found for call:
at <doctest default[2]>:1
-------------------------
Called:
mock(3.14)
Expected (any of):
mock(Type(int) & Func(POSITIVE_ONLY))
Using matchers in structured data¶
You are not only limited to use matchers in expect_call()
arguments and
keyword arguments. You can also use it inside larger structures, like dicts.
That is a side effect of the fact that matchers are implemented by
customizing standard Python’s __eq__()
operator, which is called every
time you compare one object with another. Here’s an example:
from mockify.mock import Mock
from mockify.actions import Return
from mockify.matchers import Type, List
mock = Mock('mock')
mock.expect_call({
'action': Type(str),
'params': List(Type(int), min_length=2),
}).will_repeatedly(Return(True))
We’ve recorded expectation that mock()
will be called with dict
containing action key that is a string, and params key that is a list of
integers containing at least 2 elements. Here’s how it works:
>>> mock({'action': 'sum', 'params': [2, 3]})
True
>>> mock({'action': 'sum', 'params': [2, 3, 4]})
True
>>> mock({'action': 'sum', 'params': [2]})
Traceback (most recent call last):
...
mockify.exc.UnexpectedCall: No matching expectations found for call:
at <doctest default[2]>:1
-------------------------
Called:
mock({'action': 'sum', 'params': [2]})
Expected (any of):
mock({'action': Type(str), 'params': List(Type(int), min_length=2)})
In the last example we got mockify.exc.UnexpectedCall
exception
because our params key got only one argument, while it was expected at
least 2 to be given. There is no limit of how deep you can go with your
structures.
Using matchers in custom objects¶
You can also use matchers with your objects. Like in this example:
from collections import namedtuple
from mockify.mock import Mock
from mockify.matchers import Type
Vec2 = namedtuple('Vec2', 'x, y') # (1)
Float = Type(float) # (2)
canvas = Mock('canvas')
canvas.draw_line.expect_call(
Vec2(Float, Float), Vec2(Float, Float)).\
will_repeatedly(Return(True)) # (3)
We’ve created a vector object (1), then an alias to Type(float)
(2) for a
more readable expectation composing (an _
alias for
mockify.matchers.Any
is created in same way). Finally, we’ve created
canvas mock and mocked draw_line()
method, taking start and end point
arguments in form of 2-dimensional vectors. And here’s how it works:
>>> canvas.draw_line(Vec2(0.0, 0.0), Vec2(5.0, 5.0))
True
>>> canvas.draw_line(Vec2(0, 0), Vec2(5, 5))
Traceback (most recent call last):
...
mockify.exc.UnexpectedCall: No matching expectations found for call:
at <doctest default[1]>:1
-------------------------
Called:
canvas.draw_line(Vec2(x=0, y=0), Vec2(x=5, y=5))
Expected (any of):
canvas.draw_line(Vec2(x=Type(float), y=Type(float)), Vec2(x=Type(float), y=Type(float)))
Using matchers out of Mockify library¶
Matchers are pretty generic tool that you can also use outside of Mockify - just for assertion checking. For example, if you have a code that creates some records with auto increment ID you can use a matcher from Mockify to check if that ID matches some expected criteria - especially when exact value is hard to guess:
Here’s an example code:
import itertools
_next_id = itertools.count(1) # This is private
def make_product(name, description):
return {
'id': next(_next_id),
'name': name,
'description': description
}
And here’s an example test:
from mockify.matchers import Type, Func
def test_make_product():
product = make_product('foo', 'foo desc')
assert product == {
'id': Type(int) & Func(lambda x: x > 0, 'GREATER_THAN_ZERO'),
'name' : 'foo',
'description': 'foo desc',
}
Patching imported modules¶
New in version 0.6.
With Mockify you can easily substitute imported module with a mocked version. Consider following code:
import os
def iter_dirs(path):
for name in os.listdir(path):
fullname = os.path.join(path, name)
if os.path.isdir(fullname):
yield fullname
That function generates full paths to all direct children directories of
given path. And it uses os
to make some file system operations. To
test that function without refactoring it you will have to patch some
methods of os
module. And here’s how this can be done in Mockify:
from mockify.core import satisfied, patched
from mockify.mock import Mock
from mockify.actions import Return
def test_iter_dirs():
os = Mock('os') # (1)
os.listdir.expect_call('/tmp').will_once(Return(['foo', 'bar', 'baz.txt'])) # (2)
os.path.isdir.expect_call('/tmp/foo').will_once(Return(True)) # (3)
os.path.isdir.expect_call('/tmp/bar').will_once(Return(True))
os.path.isdir.expect_call('/tmp/baz.txt').will_once(Return(False))
with patched(os): # (4)
with satisfied(os): # (5)
assert list(iter_dirs('/tmp')) == ['/tmp/foo', '/tmp/bar'] # (6)
And here’s what’s going on in presented test:
- We’ve created os mock (1) for mocking os.listdir() (2) and os.path.isdir() (3) methods,
- Then we’ve used
mockify.core.patched()
context manager (4) that does the whole magic of substituting modules matching full names of mocks with expectations recorded (which are'os.listdir'
and'os.path.isdir'
in our case) with corresponding mock objects - Finally, we’ve used
mockify.core.satisfied()
context manager (5) to ensure that all expectations are satisfied, and ran tested function (6) checking it’s result.
Note that we did not mock os.path.join()
- that will be used from
os
module.
Recording ordered expectations¶
New in version 0.6.
Mockify provides a mechanism for recording ordered expectation, i.e. expectations that can only be resolved in their declaration order. That may be crucial if you need to provide additional level of testing for parts of code that must not call given interfaces in any order. Consider this:
class InterfaceCaller:
def __init__(self, first, second):
self._first = first
self._second = second
def run(self):
# A lot of complex processing goes in here...
self._first.inform() # (1)
# ...and some in here.
self._second.inform() # (2)
We have a class that depends on two interfaces: first and second. That
class has a run()
method in which some complex processing takes place.
The result of processing is a call to both of these two interfaces, but
the order does matter; calling second before first is considered a
bug which should be discovered by tests. And here is our test:
from mockify.core import satisfied
from mockify.mock import Mock
def test_interface_caller():
first = Mock('first')
second = Mock('second')
first.inform.expect_call()
second.inform.expect_call()
caller = InterfaceCaller(first, second)
with satisfied(first, second):
caller.run()
And of course, the test passes. But will it pass if we change the order of calls in class we are testing? Of course it will, because by default the order of declared expectations is irrelevant (for as long as return values does not come into play). And here comes ordered expectations:
from mockify.core import satisfied, ordered
from mockify.mock import MockFactory
def test_interface_caller():
factory = MockFactory() # (1)
first = factory.mock('first')
second = factory.mock('second')
first.inform.expect_call()
second.inform.expect_call()
caller = InterfaceCaller(first, second)
with satisfied(factory):
with ordered(factory): # (2)
caller.run()
In the test above we’ve used mock factory (1), because ordered expectations
require all checked mocks to operate on a common session. The main difference
however is use of mockify.core.ordered()
context manager (2) which ensures that
given mocks (mocks created by factory in this case) will be called in
their declaration order. And since we’ve changed the order in tested code,
the test will no longer pass and mockify.exc.UnexpectedCallOrder
assertion will be raised:
>>> test_interface_caller()
Traceback (most recent call last):
...
mockify.exc.UnexpectedCallOrder: Another mock is expected to be called:
at <doctest default[0]>:9
-------------------------
Called:
second.inform()
Expected:
first.inform()
And that exception tells us that we’ve called second.inform()
, while it
was expected to call first.inform()
earlier.
Tips & tricks¶
Mocking functions with output parameters¶
Sometimes you will need to mock calls to interfaces that will force you to create some kind of object which later is used as an argument to that call. Value of that object is not constant, it is different on every run, so you will not be able to record adequate expectation using fixed value. Moreover, that object is changed by call to mocked interface method. How to mock that out?
This kind of problem exists in following simple class:
import io
class RemoteFileStorage:
def __init__(self, bucket):
self._bucket = bucket
def read(self, name):
buffer = io.BytesIO() # (1)
self._bucket.download('bucket-name', "uploads/{}".format(name), buffer) # (2)
return buffer.getvalue()
That class is kind of a facade on top of some cloud service for accessing
files that were previously uploaded by another part of the application. To
download the file we need to create a buffer (1) which is later passed to
bucket.download()
method (2). In production, that method downloads a file
into a buffer, but how to actually mock that in test?
Here’s a solution:
from mockify.core import satisfied
from mockify.mock import Mock
from mockify.actions import Invoke
from mockify.matchers import _
def download(payload, bucket, key, fd): # (1)
fd.write(payload)
def test_reading_file_using_remote_storage():
bucket = Mock('bucket')
bucket.download.\
expect_call('bucket-name', 'uploads/foo.txt', _).\
will_once(Invoke(download, b'spam')) # (2)
storage = RemoteFileStorage(bucket)
with satisfied(bucket):
assert storage.read('foo.txt') == b'spam' # (3)
And here’s an explanation:
- We’ve implemented
download()
function - a minimal and dummy implementation ofbucket.download()
method. Our function simply writes given payload to file descriptor created in tested class and passed as a last argument. - We’ve recorded an expectation that
bucket.download()
will be called once with three args, having last argument wildcarded usingmockify.matchers.Any
matcher. Therefore, buffer object passed to a mock will match that expectation. - We’ve recorded single
mockify.actions.Invoke
action to execute function created in (1), withb'spam'
bytes object bound as first argument (that’s whydownload()
function accepts one more argument). With this approach we can forcedownload()
to write different bytes - depending on out test. - Finally, we’ve used assertion at (3) to check if tested method returns “downloaded” bytes.
API Reference¶
mockify - Library core¶
Library core module.
Deprecated since version 0.9.0: Please import from mockify.core
module instead. Importing via
Mockify’s root module will stop working since next major release.
-
class
mockify.
Call
(*args, **kwargs)¶ Deprecated since version 0.13: This class was moved and is currently available as
mockify.core.Call
class. Old import will stop working in one of upcoming releases.
-
class
mockify.
LocationInfo
(*args, **kwargs)¶ Deprecated since version 0.13: This class was moved and is currently available as
mockify.core.LocationInfo
class. Old import will stop working in one of upcoming releases.
-
class
mockify.
Session
(*args, **kwargs)¶ Deprecated since version 0.13: This class was moved and is currently available as
mockify.core.Session
class. Old import will stop working in one of upcoming releases.
-
class
mockify.
Expectation
(*args, **kwargs)¶ Deprecated since version 0.13: This class was moved and is currently available as
mockify.core.Expectation
class. Old import will stop working in one of upcoming releases.
-
mockify.
assert_satisfied
(mock: mockify.abc.IMock, *args)¶ Deprecated since version 0.13: This function was moved and is currently available as
mockify.core.assert_satisfied()
function. Old import will stop working in one of upcoming releases.
-
mockify.
ordered
(mock: mockify.abc.IMock, *args)¶ Deprecated since version 0.13: This function was moved and is currently available as
mockify.core.ordered()
function. Old import will stop working in one of upcoming releases.
-
mockify.
satisfied
(mock: mockify.abc.IMock, *mocks)¶ Deprecated since version 0.13: This function was moved and is currently available as
mockify.core.satisfied()
function. Old import will stop working in one of upcoming releases.
-
mockify.
patched
(mock: mockify.abc.IMock, *args)¶ Deprecated since version 0.13: This function was moved and is currently available as
mockify.core.patched()
function. Old import will stop working in one of upcoming releases.
mockify.core - Library core¶
Classes and functions providing Mockify’s core functionality.
-
class
mockify.core.
MockInfo
(target: mockify.abc.IMock)¶ Bases:
object
A class for inspecting mocks.
This class simplifies the use of Mockify-defined special methods and properties that must be implemented by
mockify.abc.IMock
subclasses. It is like agetattr()
to be used on top ofobject.__getattr__
magic method.Although this class is basically a 1-1 proxy on top of
__m_(.*)__
properties provided bymockify.abc.IMock
, some methods additionally wrap results withMockInfo
instances.Parameters: target – Mock to be inspected -
__init__
(target: mockify.abc.IMock)¶ Initialize self. See help(type(self)) for accurate signature.
-
__repr__
()¶ Return repr(self).
-
children
() → Iterator[mockify.core._inspect.MockInfo]¶ Return iterator over target mock’s direct children.
This uses
mockify.abc.IMock.__m_children__()
method on a target mock behind the scenes, but wraps each returned child with a newMockInfo
object.
-
expectations
() → Iterator[mockify.abc.IExpectation]¶ Return iterator over expectation objects created on target mock.
This is equivalent of using
mockify.abc.IMock.__m_expectations__()
method on a target mock.
-
walk
() → Iterator[mockify.core._inspect.MockInfo]¶ Recursively iterate over tree structure of given target mock.
Behind the scenes this uses
mockify.abc.IMock.__m_walk__()
method on a target mock, but wraps each yielded child with a newMockInfo
object.It always yields self as a first generated item.
Changed in version 0.13: Now this is simply calling
mockify.abc.IMock.__m_walk__()
behind the scenes.
-
__weakref__
¶ list of weak references to the object (if defined)
-
fullname
¶ Return full name of target mock.
This is equivalent of using
mockify.abc.IMock.__m_fullname__
attribute on a target mock.Changed in version 0.13: Now this is not calculating full name, but simply is calling
mockify.abc.IMock.__m_fullname__
on target mock.New in version 0.8.
-
mock
¶ Target mock that is being inspected.
Deprecated since version 0.8: This is deprecated since version 0.8 and will be dropped in one of upcoming releases. Use
target
instead.
-
name
¶ Return name of target mock.
This is equivalent of using
mockify.abc.IMock.__m_name__
attribute on a target mock.Changed in version 0.8: It is no longer full name; for that purpose use new
fullname
-
parent
¶ A proxy to access
BaseMock.__m_parent__
.Returns
None
if target has no parent, or parent wrapped withMockInfo
object otherwise.New in version 0.8.
-
session
¶ Return
mockify.abc.ISession
object assigned to target mock.This is equivalent of using
mockify.abc.IMock.__m_session__
attribute on a target mock.
-
target
¶ Target mock that is being inspected.
New in version 0.8.
-
-
class
mockify.core.
BaseMock
(*args, **kwargs)¶ Bases:
mockify.mock._base.BaseMock
Deprecated since version 0.13: This class was moved and is currently available as
mockify.mock.BaseMock
class. Old import will stop working in one of upcoming releases.
-
class
mockify.core.
Call
(__m_fullname__: str, *args, **kwargs)¶ Bases:
mockify.abc.ICall
Default implementation of the
mockify.abc.ICall
interface.Parameters: - __m_fullname__ – The full name of a mock
- *args – Positional arguments
- **kwargs – Named arguments
Changed in version 0.13: Now this inherits from
mockify.abc.ICall
abstract base class-
__init__
(__m_fullname__: str, *args, **kwargs)¶ Initialize self. See help(type(self)) for accurate signature.
-
__repr__
()¶ Return repr(self).
-
__str__
()¶ Return str(self).
-
args
¶
-
kwargs
¶
-
location
¶
-
name
¶
-
class
mockify.core.
LocationInfo
(filename: str, lineno: int)¶ Bases:
mockify.core._call._CallLocation
A placeholder for file name and line number obtained from the stack.
Used by
mockify.core.Call
objects to get their location in the code. That information is later used in assertion messages.Deprecated since version 0.13: This is now made private and will be completely removed in next major release.
New in version 0.6.
Parameters: - filename – Name of the file
- lineno – Line number in given file
-
__init__
(filename: str, lineno: int)¶ Initialize self. See help(type(self)) for accurate signature.
-
class
mockify.core.
Expectation
(expected_call: mockify.abc.ICall)¶ Bases:
mockify.abc.IExpectation
Default implementation of the
mockify.abc.IExpectation
interface.Instances of this class are created and returned when expectations are recorded on mocks.
Here’s an example:
>>> from mockify.mock import Mock >>> mock = Mock('mock') >>> mock.expect_call(1, 2) <mockify.core.Expectation: mock(1, 2)>
Parameters: expected_call – Instance of
mockify.abc.ICall
object that was created during expectation recording.Note
Value of
mockify.abc.ICall.location
property of given call object will point to the place in user’s test code whereexpect_call(...)
method was called.-
__call__
(actual_call: mockify.abc.ICall)¶ Consume this expectation object.
This method requires given actual_call object to satisfy following condition:
self.expected_call == actual_call
In other words, given actual_call must be equal to current
expected_call
object, as consuming expectation with a non-matching actuall call does not make sense and is considered as a bug.Parameters: actual_call – Instance of
mockify.abc.ICall
object that was created when corresponding mock was called.Note
Value of
mockify.abc.ICall.location
property of given call object will point to the place in user’s tested code where the mocked method or function was called.
-
__init__
(expected_call: mockify.abc.ICall)¶ Initialize self. See help(type(self)) for accurate signature.
-
__repr__
()¶ Return repr(self).
-
is_satisfied
()¶
-
times
(cardinality)¶
-
will_once
(action)¶
-
will_repeatedly
(action)¶
-
action
¶ Return action to be executed when this expectation receives another call or
None
if there are no (more) actions.Return type: mockify.actions.Action
-
actual_call_count
¶ Number of matching calls this expectation object received so far.
This is relative value; if one action expires and another one is started to be executed, then the counter starts counting from 0 again. Thanks to this you’ll receive information about actual action execution count. If your expectation does not use
will_once()
orwill_repeatedly()
, then this counter will return total number of calls.New in version 0.6.
-
expected_call
¶ Returns expected call object assigned to this expectation.
This is used by Mockify’s internals to find expectations that match given actual call object.
-
expected_call_count
¶ Return object representing expected number of mock calls.
Like
actual_call_count
, this varies depending on internal expectation object state.Return type: mockify.cardinality.ExpectedCallCount
-
-
class
mockify.core.
Session
¶ Bases:
mockify.abc.ISession
A class providing core logic of connecting mock calls with recorded expectations.
Sessions are created for each mock automatically, or can be created explicitly and then shared across multiple mocks. While mock classes can be seen as som kind of frontends that mimic behavior of various Python constructs, session instances are some kind of backends that receive
mockify.core.Call
instances created by mocks during either mock call, or expectation recording.Changed in version 0.6: Previously this was named Registry.
-
__call__
(actual_call)¶
-
__init__
()¶ Initialize self. See help(type(self)) for accurate signature.
-
assert_satisfied
()¶ Check if all registered expectations are satisfied.
This works exactly the same as
mockify.core.assert_satisfied()
, but for given session only. Can be used as a replacement for any other checks if one global session object is used.
-
disable_ordered
()¶ Called by
mockify.core.ordered()
when processing of ordered expectations is done.Moves any remaining expectations back to the unordered storage, so they will be later displayed as unsatisfied.
-
enable_ordered
(names)¶ Mark expectations matching given mock names as ordered, so they will have to be resolved in their declaration order.
This is used internally by
mockify.core.ordered()
.
-
expect_call
(expected_call)¶
-
expectations
()¶
-
config
¶ A dictionary-like object for configuring sessions.
Following options are currently available:
'expectation_class'
Can be used to override expectation class used when expectations are recorded.
By default, this is
mockify.core.Expectation
, and there is a requirement that custom class must inherit from original one.'uninterested_call_strategy'
Used to set a way of processing so called unexpected calls, i.e. calls to mocks that has no expectations recorded. Following values are supported:
'fail'
This is default option.
When mock is called unexpectedly,
mockify.exc.UninterestedCall
exception is raised and test is terminated.'warn'
- Instead of raising exception,
mockify.exc.UninterestedCallWarning
warning is issued, and test continues. 'ignore'
- Unexpected calls are silently ignored.
-
-
mockify.core.
assert_satisfied
(mock: mockify.abc.IMock, *args)¶ Check if all given mocks are satisfied.
This is done by collecting all
mockify.abc.IExpectation
objects for whichmockify.abc.IExpectation.is_satisfied()
returnsFalse
.If there are unsatisfied expectations present, then
mockify.exc.Unsatisfied
exception is raised and list of found unsatisfied expectations is reported.Parameters: - mock – Mock object
- *args – Additional mock objects
-
mockify.core.
satisfied
(mock: mockify.abc.IMock, *mocks)¶ Context manager wrapper for
assert_satisfied()
.Parameters: - mock – Mock object
- *args – Additional mock objects
-
mockify.core.
ordered
(mock: mockify.abc.IMock, *args)¶ Context manager that checks if expectations in wrapped scope are consumed in same order as they were defined.
This context manager will raise
mockify.exc.UnexpectedCallOrder
assertion on first found mock that is executed out of specified order.See Recording ordered expectations for more details.
Parameters: - mock – Mock object
- *args – Additional mock objects
-
mockify.core.
patched
(mock: mockify.abc.IMock, *args)¶ Context manager that replaces imported objects and functions with their mocks using mock name as a name of patched module.
It will patch only functions or objects that have expectations recorded, so all needed expectations will have to be recorded before this context manager is used.
See Patching imported modules for more details.
Parameters: - mock – Mock object
- *args – Additional mock objects
mockify.mock - Classes for creating and inspecting mocks¶
Classes and functions used to create mocks.
Each of these can be considered as a frontend on top of
mockify.core.Session
class, which is acting as a sort of backend for
mock classes.
-
class
mockify.mock.
BaseMock
(name: str, session: mockify.abc.ISession = None, parent: mockify.abc.IMock = None)¶ Bases:
mockify.abc.IMock
Base class for all mock classes.
This class provides partial implementation of
mockify.abc.IMock
interface and common constructor used by all mocks. If you need to create custom mock, then it is better to inherit from this class at first place, as it provides some basic initialization out of the box.Parameters: - name –
Name of this mock.
This will be returned by
__m_name__
property.See
mockify.abc.IMock.__m_name__
for more information about naming mocks. - session –
Instance of
mockify.abc.ISession
to be used.If not given, parent’s session will be used (if parent exist) or a default
mockify.core.Session
session object will be created and used.Note
This option is self-exclusive with parent parameter.
- parent –
Instance of
mockify.abc.IMock
representing parent for this mock.When this parameter is given, mock implicitly uses paren’t session object.
Note
This option is self-exclusive with session parameter.
Changed in version 0.13: Moved from
mockify.core
intomockify.mock
.Changed in version 0.9: Added
__init__
method, as it is basically same for all mock classes.New in version 0.8.
-
__m_name__
¶
-
__m_parent__
¶
-
__m_session__
¶
- name –
-
class
mockify.mock.
MockFactory
(name=None, mock_class=None, **kwargs)¶ Bases:
mockify.mock._base.BaseMock
A factory class used to create groups of related mocks.
This class allows to create mocks using class given by mock_class ensuring that:
- names of created mocks are unique,
- all mocks share one common session object.
Instances of this class keep track of created mocks. Moreover, functions that would accept
Mock
instances will also acceptMockFactory
instances, so you can later f.e. check if all created mocks are satisfied using just a factory object. That makes it easy to manage multiple mocks in large test suites.See Managing multiple mocks for more details.
Changed in version 0.8: Now it inherits from
mockify.mock.BaseMock
, as this class is more or less special kind of mock.New in version 0.6.
Parameters: - name –
This is optional.
Name of this factory to be used as a common prefix for all created mocks and nested factories.
- mock_class –
The class that will be used by this factory to create mocks.
By default it will use
Mock
class.
Changed in version 0.9: Removed parameter
session
in favour of**kwargs
; session handling is now done byBaseMock
class.-
__m_children__
()¶ An iterator over
IMock
objects representing direct children of this mock.This SHOULD NOT include grandchildren.
-
__m_expectations__
()¶ An iterator over
IExpectation
objects recorded for this mock.This SHOULD NOT include expectations recorded for children of this mock.
-
factory
(name)¶ Create and return child factory.
Child factory will use session from its parent, and will prefix all mocks and grandchild factories with given name.
This method will raise
TypeError
if name is already used by either mock or child factory.Return type: MockFactory
-
class
mockify.mock.
BaseFunctionMock
(name: str, session: mockify.abc.ISession = None, parent: mockify.abc.IMock = None)¶ Bases:
mockify.mock._base.BaseMock
Foundation class for making custom function mocks, with user-defined fixed set of positional and/or keyword arguments.
Each subclass must provide pair of
expect_call
and__call__
methods that will be used to record and consume (accordingly) expectations created with user-defined fixed set of arguments. Thanks to this class the linter will no longer complain about arguments that were redefined in subclass.For easier subclassing, this class provides two helper methods for easier implementing of
__call__
andexpect_call
methods:Here’s an example:
from mockify.api import BaseFunctionMock, satisfied, Return class DummyFunctionMock(BaseFunctionMock): def __call__(self, a, b, c=None): return self.__m_call__(a, b, c=c) def expect_call(self, a, b, c=None): return self.__m_expect_call__(a, b, c=c) def call_func(func): return func(1, 2, c='spam') def test_call_func(): mock = DummyFunctionMock('mock') mock.expect_call(1, 2, c='spam').will_once(Return(123)) with satisfied(mock): assert call_func(mock) == 123
New in version 0.13.
-
__m_call__
(*args, **kwargs)¶ A helper to implement
__call__
method in a subclass.
-
__m_children__
()¶
-
__m_expect_call__
(*args, **kwargs) → mockify.abc.IExpectation¶ A helper to implement
expect_call
method in a subclass.
-
__m_expectations__
()¶
-
-
class
mockify.mock.
FunctionMock
(name: str, session: mockify.abc.ISession = None, parent: mockify.abc.IMock = None)¶ Bases:
mockify.mock._function.BaseFunctionMock
Class for mocking arbitrary Python functions.
This is most basic mock class Mockify provides. You can use it to mock free Python functions (like callbacks), or to build more complex, custom mocks with it.
Here’s example usage:
from mockify.api import satisfied, Return, FunctionMock def call_func(func): return func(1, 2, 3) def test_call_func(): func = FunctionMock('func') func.expect_call(1, 2, 3).will_once(Return(123)) with satisfied(func): assert call_func(func) == 123
Changed in version 0.9: Removed parameter
session
in favour of**kwargs
; session handling is now done byBaseMock
class.New in version 0.8.
-
__call__
(*args, **kwargs) → Optional[Any]¶ Call this mock.
-
expect_call
(*args, **kwargs) → mockify.abc.IExpectation¶ Record call expectation on this mock.
-
-
class
mockify.mock.
Mock
(name, max_depth=-1, **kwargs)¶ Bases:
mockify.mock._function.FunctionMock
General purpose mocking class.
This class can be used to:
- create mocks of free functions (f.e. callbacks) or callable objects,
- create mocks of any Python objects
- create mocks of namespaced function calls (f.e.
foo.bar.baz.spam(...)
), - create ad-hoc data objects.
No matter what you will be mocking, for all cases creating mock objects is always the same - by giving it a name and optionally session. Mock objects automatically create attributes on demand, and that attributes form some kind of nested or child mocks.
To record expectations, you have to call expect_call() method on one of that attributes, or on mock object itself (for function mocks). Then you pass mock object to unit under test. Finally, you will need
mockify.core.assert_satisfied()
function ormockify.core.satisfied()
context manager to check if the mock is satisfied.Currently supported magic methods are:
Comparison operators:
__eq__(self, other)
, i.e. ==__ne__(self, other)
, i.e. !=__lt__(self, other)
, i.e. <__gt__(self, other)
, i.e. >__le__(self, other)
, i.e. <=__ge__(self, other)
, i.e. >=
Unary arithmethic operators:
__pos__(self)
, f.e. +foo__neg__(self)
, f.e. -foo__abs__(self)
, f.e. abs(foo)__invert__(self)
, f.e. ~foo__round__(self, ndigits=None)
, f.e. round(foo) or round(foo, ndigits)__floor__(self)
, f.e. floor(foo)__ceil__(self)
, f.e. ceil(foo)
- Normal arithmethic operators, including reflected versions for all
listed below (f.e.
__radd__
for__add__
, which will be called in other + foo statement, as opposed to typical foo + other):__add__(self, other)__
, f.e. foo + other__sub__(self, other)__
, f.e. foo - other__mul__(self, other)__
, f.e. foo * other__floordiv__(self, other)__
, f.e. foo // other__div__
and__truediv__
, f.e. foo / other__mod__(self, other)__
, f.e. foo % other__divmod__(self, other)__
, f.e. divmod(foo, other)__pow__(self, other)__
, f.e.foo ** other
__lshift__(self, other)
, f.e. foo << other__rshift__(self, other)
, f.e. foo >> other__and__(self, other)
, f.e. foo & other__or__(self, other)
, f.e. foo | other__xor__(self, other)
, f.e. foo ^ other
In-place arithmethic operators:
__iadd__(self, other)__
, f.e. foo += other__isub__(self, other)__
, f.e. foo -= other__imul__(self, other)__
, f.e. foo *= other__ifloordiv__(self, other)__
, f.e. foo //= other__idiv__
and__itruediv__
, f.e. foo /= other__imod__(self, other)__
, f.e. foo %= other__ipow__(self, other)__
, f.e.foo **= other
__ilshift__(self, other)
, f.e. foo <<= other__irshift__(self, other)
, f.e. foo >>= other__iand__(self, other)
, f.e. foo &= other__ior__(self, other)
, f.e. foo |= other__ixor__(self, other)
, f.e. foo ^= other
Type conversion operators:
__str__(self)
, f.e. str(foo)__int__(self)
, f.e. int(foo)__float__(self)
, f.e. float(foo)__complex__(self)
, f.e. complex(foo)__index__(self)
, f.e. oct(foo), hex(foo) or when used- in slice expression
Class representation methods:
__repr__(self)
, f.e. repr(foo)__format__(self, formatstr)
, used whenstr.format()
is- given an instance of a mock (formatstr is then passed from
formatting options, f.e.
"{:abc}".format(foo)
will pass abc as formatstr)
__hash__(self)
, called by built-inhash()
__dir__(self)
, called by built-indir()
__sizeof__(self)
, called bysys.getsizeof()
Attribute access methods:
__getattr__(self, name)
, used bygetattr()
or when- attribute is being get (f.e.
spam = foo.spam
)
__setattr__(self, name, value)
, used bysetattr()
or- when attribute is being set (f.e.
foo.spam = 123
)
__delattr__(self, name)
, used bydelattr()
or when- when attrbute is being deleted (f.e.
del foo.spam
)
Note
If the methods above are not mocked, default implementations will be used, allowing to set/get/delete a user-defined attributes:
>>> foo = Mock('foo') >>> foo.spam = 123 >>> foo.spam 123 >>> del foo.spam
However, if explicit expectation is set, the behaviour from above will be replaced with calling adequate mock object:
foo = Mock('foo') foo.__getattr__.expect_call('spam').will_once(Return(123)) with satisfied(foo): assert foo.spam == 123
Container methods:
__len__(self)
, called bylen()
__getitem__(self, key)
, called when item is being get (f.e.spam = foo['spam']
)
__setitem__(self, key, value)
, called when item is being set- (f.e.
foo['spam'] = 123
)
__delitem__(self, key)
, called when item is being deleted- (f.e.
del foo['spam']
)
__reversed__(self)
, called byreversed()
__contains__(self, key)
, called when mock is tested for- presence of given item (f.e.
if 'spam' in foo:
)
Generator methods:
__call__ method:
__call__(self, *args, **kwargs)
Note
Recording expectation explicitly on
object.__call__()
magic method is equivalent to recording call expectation directly on a mock object:foo = Mock('foo') foo.__call__.expect_call('one').will_once(Return(1)) foo.expect_call('two').will_once(Return(2)) with satisfied(foo): assert foo('one') == 1 assert foo('two') == 2
Context management methods:
__enter__(self)
__aenter__(self)
__exit__(self, exc, exc_type, tb)
__aexit__(self, exc, exc_type, tb)
Descriptor methods:
__get__(self, obj, obj_type)
__set__(self, obj, value)
__delete__(self, obj)
Here’s an example:
from mockify.core import satisfied from mockify.mock import Mock def caller(func, a, b): func(a + b) def test_caller(): func = Mock('func') func.expect_call(5) with satisfied(func): caller(func, 2, 3)
See Creating mocks and recording expectations for more details.
Changed in version 0.13: Changelog:
- Now
Mock
inherits frommockify.mock.FunctionMock
to avoid code duplication - Added max_depth parameter
- Added support for (almost) all magic methods
Changed in version 0.8: Now this class inherits from
mockify.mock.BaseMock
New in version 0.6.
-
__m_children__
()¶
-
expect_call
(*args, **kwargs)¶ Record call expectation on this mock.
-
mockify.mock.
ABCMock
(name, abstract_base_class, **kwargs)¶ Factory function for creating mocks that implement given
abc.ABC
subclass.This class is meant to be used with interfaces containing abstract methods. It performs a lookup on the interface and allows to record expectations only on methods that are defined in the interface. Moreover, it also checks argument names and disallow recording calls with invalid arguments; everything must match the definition of the interface.
Here’s an example:
import abc from mockify.core import satisfied from mockify.mock import ABCMock from mockify.actions import Return class Interface(abc.ABC): @abc.abstractmethod def method(self, a, b): pass mock = ABCMock('mock', Interface) mock.method.expect_call(1, 2).will_once(Return(123)) with satisfied(mock): assert mock.method(1, 2) == 123
Parameters: - name – Mock name
- abstract_base_class – Subclass of
abc.ABC
to be used as source of abstract methods that will be implemented by this mock.
Changed in version 0.9: * Now this is a factory function returning mock object * Parameter
session
is removed in favour of**kwargs
; session is now handled byBaseMock
classNew in version 0.8.
mockify.actions - Classes for recording side effects¶
Module containing action types.
Actions are used to tell the mock what it must do once called with given set of arguments. This can be returning a value, returning a generator, calling a function, raising exception etc.
-
class
mockify.actions.
Action
¶ Bases:
mockify.abc.IAction
,mockify._utils.DictEqualityMixin
Abstract base class for actions.
This is common base class for all actions defined in this module. Custom actions should also inherit from this one.
New in version 0.6.
-
__call__
(actual_call)¶ Action body.
It receives actual call object and returns action result based on that call object. This method may also raise exceptions if that is functionality of the action being implemented.
Parameters: actual_call – Instance of mockify.core.Call
containing params of actual call being made
-
__str__
()¶ Return string representation of this action.
This is later used in error messages when test fails.
Changed in version 0.11: Now this is made abstract and previous abstract
format_params()
was removed
-
-
class
mockify.actions.
Return
(value)¶ Bases:
mockify.actions.Action
Forces mock to return value when called.
For example:
>>> from mockify.mock import Mock >>> from mockify.actions import Return >>> mock = Mock('mock') >>> mock.expect_call().will_once(Return('foo')) <mockify.core.Expectation: mock()> >>> mock() 'foo'
-
__call__
(actual_call)¶ Action body.
It receives actual call object and returns action result based on that call object. This method may also raise exceptions if that is functionality of the action being implemented.
Parameters: actual_call – Instance of mockify.core.Call
containing params of actual call being made
-
__str__
()¶ Return string representation of this action.
This is later used in error messages when test fails.
Changed in version 0.11: Now this is made abstract and previous abstract
format_params()
was removed
-
-
class
mockify.actions.
ReturnAsync
(value)¶ Bases:
mockify.actions.Return
Similar to
Return
, but to be used with asynchronous Python code.For example:
from mockify.core import satisfied from mockify.mock import Mock from mockify.actions import ReturnAsync async def async_caller(func): return await func() async def test_async_caller(): func = Mock('func') func.expect_call().will_once(ReturnAsync('foo')) with satisfied(func): assert await async_caller(func) == 'foo'
New in version 0.11.
-
__call__
(actual_call)¶ Action body.
It receives actual call object and returns action result based on that call object. This method may also raise exceptions if that is functionality of the action being implemented.
Parameters: actual_call – Instance of mockify.core.Call
containing params of actual call being made
-
-
class
mockify.actions.
ReturnContext
(value)¶ Bases:
mockify.actions.Return
Similar to
Return
, but returns value via context manager.For example:
from mockify.core import satisfied from mockify.mock import MockFactory from mockify.actions import Return, ReturnContext class UserStorage: def __init__(self, database): self._database = database def get(self, user_id): with self._database.begin_transaction() as transaction: return transaction.users.get(user_id) def test_user_storage_get(): factory = MockFactory() transaction = factory.mock('transaction') transaction.users.get.expect_call(123).will_once(Return('user-123')) database = factory.mock('database') database.begin_transaction.expect_call().will_once(ReturnContext(transaction)) with satisfied(factory): assert UserStorage(database).get(123) == 'user-123'
New in version 0.12.
-
__call__
(actual_call)¶ Action body.
It receives actual call object and returns action result based on that call object. This method may also raise exceptions if that is functionality of the action being implemented.
Parameters: actual_call – Instance of mockify.core.Call
containing params of actual call being made
-
-
class
mockify.actions.
ReturnAsyncContext
(value)¶ Bases:
mockify.actions.ReturnContext
Similar to
ReturnContext
, but returns value via async context manager.For example:
from mockify.core import satisfied from mockify.mock import MockFactory from mockify.actions import Return, ReturnAsyncContext class UserStorage: def __init__(self, database): self._database = database async def get(self, user_id): async with self._database.begin_transaction() as transaction: return await transaction.users.get(user_id) async def test_user_storage_async_get(): factory = MockFactory() transaction = factory.mock('transaction') transaction.users.get.expect_call(123).will_once(ReturnAsync('user-123')) database = factory.mock('database') database.begin_transaction.expect_call().will_once(ReturnAsyncContext(transaction)) with satisfied(factory): assert await UserStorage(database).get(123) == 'user-123'
New in version 0.12.
-
__call__
(actual_call)¶ Action body.
It receives actual call object and returns action result based on that call object. This method may also raise exceptions if that is functionality of the action being implemented.
Parameters: actual_call – Instance of mockify.core.Call
containing params of actual call being made
-
-
class
mockify.actions.
Iterate
(iterable)¶ Bases:
mockify.actions.Action
Similar to
Return
, but returns an iterator to given iterable.For example:
>>> from mockify.mock import Mock >>> from mockify.actions import Iterate >>> mock = Mock('mock') >>> mock.expect_call().will_once(Iterate('foo')) <mockify.core.Expectation: mock()> >>> next(mock()) 'f'
New in version 0.6.
-
__call__
(actual_call)¶ Action body.
It receives actual call object and returns action result based on that call object. This method may also raise exceptions if that is functionality of the action being implemented.
Parameters: actual_call – Instance of mockify.core.Call
containing params of actual call being made
-
__str__
()¶ Return string representation of this action.
This is later used in error messages when test fails.
Changed in version 0.11: Now this is made abstract and previous abstract
format_params()
was removed
-
-
class
mockify.actions.
IterateAsync
(iterable)¶ Bases:
mockify.actions.Iterate
Similar to
Iterate
, but returns awaitable that returns an iterator to given iterable.For example:
from mockify.core import satisfied from mockify.mock import Mock from mockify.actions import IterateAsync async def get_next(func): iterable = await func() return next(iterable) async def test_get_next(): func = Mock('func') func.expect_call().will_once(IterateAsync('foo')) with satisfied(func): assert await get_next(func) == 'f'
New in version 0.11.
-
__call__
(actual_call)¶ Action body.
It receives actual call object and returns action result based on that call object. This method may also raise exceptions if that is functionality of the action being implemented.
Parameters: actual_call – Instance of mockify.core.Call
containing params of actual call being made
-
-
class
mockify.actions.
YieldAsync
(iterable)¶ Bases:
mockify.actions.Iterate
Similar to
Iterate
, but returns async iterator to given iterable.This iterator can later be used with
async for
statement.For example:
from mockify.core import satisfied from mockify.mock import Mock from mockify.actions import YieldAsync async def fetch(func): result = [] async for item in func(): result.append(item) return result async def test_fetch(): func = Mock('func') func.expect_call().will_once(YieldAsync('foo')) with satisfied(func): assert await fetch(func) == ['f', 'o', 'o']
New in version 0.12.
-
__call__
(actual_call)¶ Action body.
It receives actual call object and returns action result based on that call object. This method may also raise exceptions if that is functionality of the action being implemented.
Parameters: actual_call – Instance of mockify.core.Call
containing params of actual call being made
-
-
class
mockify.actions.
Raise
(exc)¶ Bases:
mockify.actions.Action
Forces mock to raise exc when called.
For example:
>>> from mockify.mock import Mock >>> from mockify.actions import Raise >>> mock = Mock('mock') >>> mock.expect_call().will_once(Raise(ValueError('invalid value'))) <mockify.core.Expectation: mock()> >>> mock() Traceback (most recent call last): ... ValueError: invalid value
-
__call__
(actual_call)¶ Action body.
It receives actual call object and returns action result based on that call object. This method may also raise exceptions if that is functionality of the action being implemented.
Parameters: actual_call – Instance of mockify.core.Call
containing params of actual call being made
-
__str__
()¶ Return string representation of this action.
This is later used in error messages when test fails.
Changed in version 0.11: Now this is made abstract and previous abstract
format_params()
was removed
-
-
class
mockify.actions.
RaiseAsync
(exc)¶ Bases:
mockify.actions.Raise
Similar to
Raise
, but to be used with asynchronous Python code.For example:
from mockify.core import satisfied from mockify.mock import Mock from mockify.actions import RaiseAsync async def async_caller(func): try: return await func() except ValueError as e: return str(e) async def test_async_caller(): func = Mock('func') func.expect_call().will_once(RaiseAsync(ValueError('an error'))) with satisfied(func): assert await async_caller(func) == 'an error'
New in version 0.11.
-
__call__
(actual_call)¶ Action body.
It receives actual call object and returns action result based on that call object. This method may also raise exceptions if that is functionality of the action being implemented.
Parameters: actual_call – Instance of mockify.core.Call
containing params of actual call being made
-
-
class
mockify.actions.
Invoke
(func, *args, **kwargs)¶ Bases:
mockify.actions.Action
Forces mock to invoke func when called.
When func is called, it is called with all bound arguments plus all arguments mock was called with. Value that mock returns is the one func returns. Use this action when more sophisticated checks have to be done when mock gets called or when your mock must operate on some output parameter.
See Mocking functions with output parameters for more details.
Here’s an example using one of built-in functions as a func:
>>> from mockify.mock import Mock >>> from mockify.actions import Invoke >>> mock = Mock('mock') >>> mock.expect_call([1, 2, 3]).will_once(Invoke(sum)) <mockify.core.Expectation: mock([1, 2, 3])> >>> mock([1, 2, 3]) 6
Changed in version 0.6: Now this action allows binding args to function being called.
Parameters: - func – Function to be executed
- args – Additional positional args to be bound to
func
. - kwargs – Additional named args to be bound to
func
.
-
__call__
(actual_call)¶ Action body.
It receives actual call object and returns action result based on that call object. This method may also raise exceptions if that is functionality of the action being implemented.
Parameters: actual_call – Instance of mockify.core.Call
containing params of actual call being made
-
__str__
()¶ Return string representation of this action.
This is later used in error messages when test fails.
Changed in version 0.11: Now this is made abstract and previous abstract
format_params()
was removed
-
class
mockify.actions.
InvokeAsync
(func, *args, **kwargs)¶ Bases:
mockify.actions.Invoke
Similar to
Invoke
, but to be used with asynchronous Python code.This action can be instantiated with either non-async or async func. No matter which one you pick, it always makes mock awaitable. Here’s an example showing usage with both callback function types:
from mockify.core import satisfied from mockify.mock import Mock from mockify.actions import InvokeAsync from mockify.matchers import _ async def test_invoke_async_with_non_async_func(): def func(numbers): return sum(numbers) mock = Mock('func') mock.expect_call(_).will_once(InvokeAsync(func)) with satisfied(mock): assert await mock([1, 2, 3]) == 6 async def test_invoke_async_with_async_func(): async def async_func(numbers): return sum(numbers) mock = Mock('func') mock.expect_call(_).will_once(InvokeAsync(async_func)) with satisfied(mock): assert await mock([1, 2, 3]) == 6
New in version 0.11.
-
__call__
(actual_call)¶ Action body.
It receives actual call object and returns action result based on that call object. This method may also raise exceptions if that is functionality of the action being implemented.
Parameters: actual_call – Instance of mockify.core.Call
containing params of actual call being made
-
mockify.cardinality - Classes for setting expected call cardinality¶
Module containing types to be used to set expected number of mock calls.
-
class
mockify.cardinality.
ActualCallCount
(initial_value)¶ Bases:
object
Proxy class that is used to calculate actual mock calls.
Provides all needed arithmetic operators and a logic of rendering actual call message that is used in assertion messages.
Here’s an example:
>>> from mockify.cardinality import ActualCallCount >>> str(ActualCallCount(0)) 'never called' >>> str(ActualCallCount(1)) 'called once'
New in version 0.6.
-
__repr__
()¶ Return repr(self).
-
__str__
()¶ Return str(self).
-
value
¶ Number of actual mock calls.
-
-
class
mockify.cardinality.
ExpectedCallCount
¶ Bases:
mockify.abc.IExpectedCallCountMatcher
,mockify._utils.DictEqualityMixin
Abstract base class for classes used to set expected call count on mock objects.
New in version 0.6.
-
__repr__
()¶ Return textual representation of expected call count object.
Since
__str__()
is used to render textual message of expected call count, this should render actual object representation, i.e. module, name, params it was created with etc.Changed in version 0.11: Now this is made abstract and previous
format_params()
was removed.
-
__str__
()¶ Format message to be used in assertion reports.
This message must state how many times the mock was expected to be called and will only be evaluated if expectation is not satisfied.
-
adjust_minimal
(minimal)¶ Make a new cardinality object based on its current state and given minimal.
This produces a new
ExpectedCallCount
instance, but taking into account that some restrictions are already specified, f.e. with use ofSession.will_once()
.
-
match
(actual_call_count)¶ Check if actual_call_count matches expected call count.
-
-
class
mockify.cardinality.
Exactly
(expected)¶ Bases:
mockify.cardinality.ExpectedCallCount
Used to set expected call count to fixed expected value.
Expectations marked with this cardinality object will have to be called exactly expected number of times to be satisfied.
You do not have to use this class explicitly as its instances are automatically created when you call
mockify.core.Expectation.times()
method with integer value as argument.-
__repr__
()¶ Return textual representation of expected call count object.
Since
__str__()
is used to render textual message of expected call count, this should render actual object representation, i.e. module, name, params it was created with etc.Changed in version 0.11: Now this is made abstract and previous
format_params()
was removed.
-
__str__
()¶ Format message to be used in assertion reports.
This message must state how many times the mock was expected to be called and will only be evaluated if expectation is not satisfied.
-
adjust_minimal
(minimal)¶ Make a new cardinality object based on its current state and given minimal.
This produces a new
ExpectedCallCount
instance, but taking into account that some restrictions are already specified, f.e. with use ofSession.will_once()
.
-
match
(actual_call_count)¶ Check if actual_call_count matches expected call count.
-
-
class
mockify.cardinality.
AtLeast
(minimal)¶ Bases:
mockify.cardinality.ExpectedCallCount
Used to set expected call count to given minimal value.
Expectation will be satisfied if called not less times that given minimal.
-
__repr__
()¶ Return textual representation of expected call count object.
Since
__str__()
is used to render textual message of expected call count, this should render actual object representation, i.e. module, name, params it was created with etc.Changed in version 0.11: Now this is made abstract and previous
format_params()
was removed.
-
__str__
()¶ Format message to be used in assertion reports.
This message must state how many times the mock was expected to be called and will only be evaluated if expectation is not satisfied.
-
adjust_minimal
(minimal)¶ Make a new cardinality object based on its current state and given minimal.
This produces a new
ExpectedCallCount
instance, but taking into account that some restrictions are already specified, f.e. with use ofSession.will_once()
.
-
match
(actual_call_count)¶ Check if actual_call_count matches expected call count.
-
-
class
mockify.cardinality.
AtMost
(maximal)¶ Bases:
mockify.cardinality.ExpectedCallCount
Used to set expected call count to given maximal value.
If this is used, then expectation is said to be satisfied if actual call count is not greater than maximal.
-
__repr__
()¶ Return textual representation of expected call count object.
Since
__str__()
is used to render textual message of expected call count, this should render actual object representation, i.e. module, name, params it was created with etc.Changed in version 0.11: Now this is made abstract and previous
format_params()
was removed.
-
__str__
()¶ Format message to be used in assertion reports.
This message must state how many times the mock was expected to be called and will only be evaluated if expectation is not satisfied.
-
adjust_minimal
(minimal)¶ Make a new cardinality object based on its current state and given minimal.
This produces a new
ExpectedCallCount
instance, but taking into account that some restrictions are already specified, f.e. with use ofSession.will_once()
.
-
match
(actual_call_count)¶ Check if actual_call_count matches expected call count.
-
-
class
mockify.cardinality.
Between
(minimal, maximal)¶ Bases:
mockify.cardinality.ExpectedCallCount
Used to set a range of expected call counts between minimal and maximal, both included.
If this is used, then expectation is said to be satisfied if actual call count is not less than minimal and not greater than maximal.
-
__repr__
()¶ Return textual representation of expected call count object.
Since
__str__()
is used to render textual message of expected call count, this should render actual object representation, i.e. module, name, params it was created with etc.Changed in version 0.11: Now this is made abstract and previous
format_params()
was removed.
-
__str__
()¶ Format message to be used in assertion reports.
This message must state how many times the mock was expected to be called and will only be evaluated if expectation is not satisfied.
-
adjust_minimal
(minimal)¶ Make a new cardinality object based on its current state and given minimal.
This produces a new
ExpectedCallCount
instance, but taking into account that some restrictions are already specified, f.e. with use ofSession.will_once()
.
-
match
(actual_call_count)¶ Check if actual_call_count matches expected call count.
-
mockify.matchers - Classes for wildcarding expected arguments¶
Module with types representing matchers.
Matchers are used to wildcard some expected parameters when expectation is recorded. Matchers do that by overloading (in)equality operator in their specific way. With this you can record expectations using value ranges, type checking, regular expressions and more.
-
class
mockify.matchers.
Matcher
¶ Bases:
abc.ABC
Abstract base class for matchers.
Changed in version 0.6: Now this inherits from
abc.ABC
-
__eq__
(other)¶ Check if other can be accepted by this matcher.
-
__repr__
()¶ Return textual representation of this matcher.
Returned string representation is later used in error reporting.
-
-
class
mockify.matchers.
AnyOf
(*values)¶ Bases:
mockify.matchers.Matcher
Matches any value from given list of values.
You can also use matchers in values.
New in version 0.6.
-
__eq__
(other)¶ Check if other can be accepted by this matcher.
-
__repr__
()¶ Return textual representation of this matcher.
Returned string representation is later used in error reporting.
-
-
class
mockify.matchers.
AllOf
(*values)¶ Bases:
mockify.matchers.Matcher
Matches if and only if received value is equal to all given values.
You can also use matchers in values.
New in version 0.6.
-
__eq__
(other)¶ Check if other can be accepted by this matcher.
-
__repr__
()¶ Return textual representation of this matcher.
Returned string representation is later used in error reporting.
-
-
class
mockify.matchers.
Any
¶ Bases:
mockify.matchers.Matcher
Matches any value.
This can be used as a wildcard, when you care about number of arguments in your expectation, not their values or types.
Note
This is also available as
_
(underscore) member ofmockify.matchers
module:from mockify.matchers import _, Any assert isinstance(_, Any)
-
__eq__
(other)¶ Check if other can be accepted by this matcher.
-
__repr__
()¶ Return textual representation of this matcher.
Returned string representation is later used in error reporting.
-
-
class
mockify.matchers.
Type
(*types)¶ Bases:
mockify.matchers.Matcher
Matches any value that is instance of one of given types.
This is useful to record expectations where we do not care about expected value, but we do care about expected value type.
New in version 0.6.
-
__eq__
(other)¶ Check if other can be accepted by this matcher.
-
__repr__
()¶ Return textual representation of this matcher.
Returned string representation is later used in error reporting.
-
-
class
mockify.matchers.
Regex
(pattern, name=None)¶ Bases:
mockify.matchers.Matcher
Matches value if it is a string that matches given regular expression pattern.
Parameters: - pattern – Regular expression pattern
- name –
Optional name for given pattern.
If given, then name will be used in text representation of this matcher. This can be very handy, especially when regular expression is complex and hard to read. Example:
>>> r = Regex(r'^[a-z]+$', 'LOWER_ASCII') >>> repr(r) 'Regex(LOWER_ASCII)'
New in version 0.6.
-
__eq__
(other)¶ Check if other can be accepted by this matcher.
-
__repr__
()¶ Return textual representation of this matcher.
Returned string representation is later used in error reporting.
-
class
mockify.matchers.
List
(matcher, min_length=None, max_length=None)¶ Bases:
mockify.matchers.Matcher
Matches value if it is a list of values matching matcher.
Parameters: - matcher –
A matcher that every value in the list is expected to match.
Use
Any
matcher if you want to match list containing any values. - min_length – Minimal accepted list length
- max_length – Maximal accepted list length
New in version 0.6.
-
__eq__
(other)¶ Check if other can be accepted by this matcher.
-
__repr__
()¶ Return textual representation of this matcher.
Returned string representation is later used in error reporting.
- matcher –
-
class
mockify.matchers.
Object
(**kwargs)¶ Bases:
mockify.matchers.Matcher
Matches value if it is an object with attributes equal to names and values given via keyword args.
This matcher creates ad-hoc object using provided keyword args. These args are then used to compare with value’s attributes of same name. All attributes must match for this matcher to accept value.
Here’s an example:
from collections import namedtuple from mockify.core import satisfied from mockify.mock import Mock from mockify.matchers import Object CallArg = namedtuple('CallArg', 'foo, bar') mock = Mock('mock') mock.expect_call(Object(foo=1, bar=2)) with satisfied(mock): mock(CallArg(1, 2))
New in version 0.6.5.
Parameters: **kwargs – Arguments to compare value with -
__eq__
(other)¶ Check if other can be accepted by this matcher.
-
__repr__
()¶ Return textual representation of this matcher.
Returned string representation is later used in error reporting.
-
-
class
mockify.matchers.
Func
(func, name=None)¶ Bases:
mockify.matchers.Matcher
Matches value if func returns
True
for that value.This is the most generic matcher as you can use your own match function if needed.
Parameters: - func – Function to be used to calculate match.
- name –
Optional name for this matcher.
This can be used to set a name used to format matcher’s text representation for assertion errors. Here’s a simple example:
>>> f = Func(lambda x: x > 0, 'POSITIVE_ONLY') >>> repr(f) 'Func(POSITIVE_ONLY)'
New in version 0.6.
-
__eq__
(other)¶ Check if other can be accepted by this matcher.
-
__repr__
()¶ Return textual representation of this matcher.
Returned string representation is later used in error reporting.
mockify.exc - Library exceptions¶
Module containing Mockify’s warnings and exceptions.
-
exception
mockify.exc.
MockifyWarning
¶ Bases:
Warning
Common base class for Mockify warnings.
New in version 0.6.
-
exception
mockify.exc.
UninterestedCallWarning
¶ Bases:
mockify.exc.MockifyWarning
This warning is used to inform about uninterested call being made.
It is only used when uninterested call strategy is changed in mocking session. See
mockify.core.Session
for more details.New in version 0.6.
-
exception
mockify.exc.
MockifyError
¶ Bases:
Exception
Common base class for all Mockify exceptions.
New in version 0.6.
-
exception
mockify.exc.
MockifyAssertion
¶ Bases:
mockify.exc.MockifyError
,AssertionError
Common base class for all Mockify assertion errors.
With this exception it will be easy to re-raise Mockify-specific assertion exceptions for example during debugging.
New in version 0.6.
-
exception
mockify.exc.
UnexpectedCall
(actual_call, expected_calls)¶ Bases:
mockify.exc.MockifyAssertion
Raised when mock was called with parameters that couldn’t been matched to any of existing expectations.
This exception was added for easier debugging of failing tests; unlike
UninterestedCall
exception, this one signals that there are expectations set for mock that was called.For example, we have expectation defined like this:
from mockify.mock import Mock mock = Mock('mock') mock.expect_call(1, 2)
And if the mock is now called f.e. without params, this exception will be raised:
>>> mock() Traceback (most recent call last): ... mockify.exc.UnexpectedCall: No matching expectations found for call: at <doctest default[0]>:1 ------------------------- Called: mock() Expected (any of): mock(1, 2)
New in version 0.6.
Parameters: - actual_call – Instance of
mockify.core.Call
representing parameters of call that was made - expected_calls – List of
mockify.core.Call
instances, each representing expected parameters of single expectation
-
actual_call
¶ Instance of
mockify.core.Call
with actual call.
-
expected_calls
¶ Sequence of
mockify.core.Call
instances with expected calls.
- actual_call – Instance of
-
exception
mockify.exc.
UnexpectedCallOrder
(actual_call, expected_call)¶ Bases:
mockify.exc.MockifyAssertion
Raised when mock was called but another one is expected to be called before.
This can only be raised if you use ordered expectations with
mockify.core.ordered()
context manager.See Recording ordered expectations for more details.
New in version 0.6.
Parameters: - actual_call – The call that was made
- expected_call – The call that is expected to be made
-
actual_call
¶ Instance of
mockify.core.Call
with actual call.
-
expected_call
¶ Instance of
mockify.core.Call
with expected call.
-
exception
mockify.exc.
UninterestedCall
(actual_call)¶ Bases:
mockify.exc.MockifyAssertion
Raised when call is made to a mock that has no expectations set.
This exception can be disabled by changing unexpected call strategy using
mockify.Session.config
attribute (however, you will have to manually create and share session object to change that).Parameters: actual_call – The call that was made -
actual_call
¶ Instance of
mockify.core.Call
with actual call.
-
-
exception
mockify.exc.
OversaturatedCall
(actual_call, oversaturated_expectation)¶ Bases:
mockify.exc.MockifyAssertion
Raised when mock with actions recorded using
mockify.core.Expectation.will_once()
was called more times than expected and has all recorded actions already consumed.This exception can be avoided if you record repeated action to the end of expected action chain (using
mockify.core.Expectation.will_repeatedly()
). However, it was added for a reason. For example, if your mock returns value of incorrect type (the default one), you’ll result in production code errors instead of mock errors. And that can possibly be harder to debug.Parameters: - actual_call – The call that was made
- oversaturated_expectation – The expectation that was oversaturated
-
actual_call
¶ Instance of
mockify.core.Call
with actual call.
-
oversaturated_expectation
¶ Instance of
mockify.core.Expectation
class representing expectation that was oversaturated.
-
exception
mockify.exc.
Unsatisfied
(unsatisfied_expectations)¶ Bases:
mockify.exc.MockifyAssertion
Raised when unsatisfied expectations are present.
This can only be raised by either
mockify.core.satisfied()
mockify.core.assert_satisfied()
ormockify.core.Session.done()
. You’ll not get this exception when mock is called.Parameters: unsatisfied_expectations – List of all unsatisfied expectations found -
unsatisfied_expectations
¶ List of unsatisfied expectations.
New in version 0.6: Previously it was called
expectations
.
-
mockify.abc - ABC classes for Mockify¶
New in version 0.13.
ABC classes for Mockify.
-
class
mockify.abc.
ICallLocation
¶ Bases:
abc.ABC
An interface to be implemented by classes used to obtain call location (file name and line number) from the stack.
This is used when instance of
ICall
is created to find a place in the code, where particular call object was created.Instances of
ICallLocation
can be checked for (in)equality. Two call location objects are equal if and only if:-
filename
¶ File name.
-
-
class
mockify.abc.
ICall
¶ Bases:
abc.ABC
An interface to be implemented by objects containing mock call information.
This information include:
- full name of the mock
- positional and keyword arguments the mock was called or is expected to be called with
- location in user’s code under test that created this mock object
Call objects are created by mocks when mock receives a call from code being under test (so called actual call), or when expectation is recorded on a mock (so called expected call). Since call objects can be tested for (in)equality, Mockify internals can easily decide if expectation was met or not.
-
args
¶ Positional args passed to the call object during creation.
-
kwargs
¶ Named args passed to the call object during creation.
-
location
¶ A place in user’s source code where this call object was created.
-
name
¶ Full name of a mock for which this object was created.
-
class
mockify.abc.
IAction
¶ Bases:
abc.ABC
An interface to be implemented by mock actions.
Actions are registered on mocks with a help of
IExpectation.will_once()
andIExpectation.will_repeatedly()
methods and are used to set what the mock must do once called by code being under test. The most trivial action is to set a mock return value, or force it to raise given exception. Please proceed tomockify.actions
to get familiar with a bulk of built-in implementations.
-
class
mockify.abc.
IExpectedCallCountMatcher
¶ Bases:
abc.ABC
An interface to be implemented by classes that set expected call count ranges.
Instances of this class are used by
IExpectation.times()
andIExpectation.IWillRepeatedlyMutation.times()
methods to set how many times the mock is expected to be called. You can find built-in implementations of this interface inmockify.cardinality
module.-
match
(actual_call_count: int) → bool¶ Check if actual number of calls matches expected number of calls.
-
-
class
mockify.abc.
IExpectation
¶ Bases:
abc.ABC
Represents single expectation recorded on a mock.
Instances of this class are created by mock’s expect_call() method or by using functions from
mockify.expect
module.-
class
IWillOnceMutation
¶ Bases:
abc.ABC
Provides return value annotation and interface for will_once() methods.
-
will_once
(action: mockify.abc.IAction) → mockify.abc.IExpectation.IWillOnceMutation¶ Attach next action to the end of action chain of current expectation object.
See
IExpectation.will_once()
for more details.
-
will_repeatedly
(action: mockify.abc.IAction) → mockify.abc.IExpectation.IWillRepeatedlyMutation¶ Finalize action chain with a repeated action.
See
IExpectation.will_repeatedly()
for more details.
-
-
class
IWillRepeatedlyMutation
¶ Bases:
abc.ABC
Provides return value annotation and interface for will_repeatedly() methods.
-
times
(value: Union[int, mockify.abc.IExpectedCallCountMatcher])¶ Used to configure how many times repeated action is expected to be called by a mock.
See
IExpectation.times()
for more details.
-
-
is_satisfied
() → bool¶ Check if this expectation object is satisfied.
Expectations are satisfied if and only if:
- all recorded one-shot actions were consumed
- number of calls being made is within expected call count range
This method is used by Mockify’s internals to collect all unsatisfied expectations and raise
mockify.exc.Unsatisfied
exception.
-
times
(value: Union[int, mockify.abc.IExpectedCallCountMatcher])¶ Set expected call count of this expectation object.
Following values are possible:
- when
int
parameter is used as a value, then expectation is expected to be called exactly value times, - when
IExpectedCallCountMatcher
object is used as a value, then number of allowed expected calls depends on particular object that was used as a value (whether it was minimal, maximal or a range of expected call counts)
See Setting expected call count tutorial section for more details.
Parameters: value – Expected call count value.
This can be either a positive integer number (for a fixed expected call count), or an instance of
IExpectedCallCountMatcher
class (for a more sophisticated expected call counts, like ranges etc.)- when
-
will_once
(action: mockify.abc.IAction) → mockify.abc.IExpectation.IWillOnceMutation¶ Attach next one-shot action to the end of the action chain of this expectation.
When expectation is consumed, actions are consumed as well. If expectation is consumed for the first time, then first action is called. If it is consumed for the second time, then second action is consumed and so on. If there are no more actions and mock is called again, then
mockify.exc.OversaturatedCall
exception is raised.See Recording actions for more details about action chains.
Parameters: action – Action to be performed
-
will_repeatedly
(action: mockify.abc.IAction) → mockify.abc.IExpectation.IWillRepeatedlyMutation¶ Finalize action chain with a repeated action.
Repeated actions can by default be invoked indefinitely by mock, unless expected call count is set explicitly with
mockify.abc.IExpectation.IWillRepeatedlyMutation.times()
method on a returned object. This is also a good way to set mock’s default action.Since repeated actions act in a different way than one-time actions, there is currently not possible to record one-time actions after repeated action is set.
See Repeated actions for more details about repeated actions.
Parameters: action – Action to be performed
-
class
-
class
mockify.abc.
ISession
¶ Bases:
abc.ABC
An interface to be implemented by session classes.
In Mockify, sessions are used to keep track of recorded and consumed expectations of each mock sharing one session object. Every created
IExpectation
object is stored in the session attached to a mock being used and every call made to that mock is reported in that session. Thanks to this, Mockify can tell (at the end of each test) which expectations were consumed, and which were not.-
__call__
(actual_call: mockify.abc.ICall) → Optional[Any]¶ Called when mock is being called.
This method is called when any mock that has this session assigned is being called. Values returned or exceptions raised by this method are also returned or raised by a mock.
Parameters: actual_call – Actual call object forwarded by caller
-
expect_call
(expected_call: mockify.abc.ICall) → mockify.abc.IExpectation¶ Factory method that creates and registers expectations inside this session.
This is called by any mock that has this session assigned during expectation recording on that mock.
Parameters: expected_call – Expected call object forwarded by caller
-
expectations
() → Iterator[mockify.abc.IExpectation]¶ Return iterator over all registered expectations.
-
-
class
mockify.abc.
IMock
¶ Bases:
abc.ABC
An interface to be implemented by mock classes.
In Mockify, mocks are organized in a tree-like structure. For example, to mock object with methods, Mockify is first creating a “root” mock representing object, and then supplies it with child nodes, each pointing to “root” as a parent, and each representing single mocked method of that object.
-
__m_children__
() → Iterator[mockify.abc.IMock]¶ An iterator over
IMock
objects representing direct children of this mock.This SHOULD NOT include grandchildren.
-
__m_expectations__
() → Iterator[mockify.abc.IExpectation]¶ An iterator over
IExpectation
objects recorded for this mock.This SHOULD NOT include expectations recorded for children of this mock.
-
__m_walk__
() → Iterator[mockify.abc.IMock]¶ Recursively iterate over mock subtree, from root to leafs, using self as a root.
This method does that by recursively iterating over
__m_children__()
iterator.It always yields self as first element.
-
__m_fullname__
¶ Full name of this mock.
This is calculated by prefixing
__m_name__
of this mock with a__m_fullname__
property of this mock’s parent, using.
as a separator.If this mock has no parent, or parent does not have a name assigned, then this will be the same as
__m_name__
.
-
__m_name__
¶ Name of this mock.
Mock names are used for error reporting, to precisely point to mock and method that caused error. For root mocks you will have to provide names manually, and for leaf mocks the names will be picked automatically, using name of a method that is being mocked.
This MUST BE a valid Python identifier, or a sequence of valid Python identifiers concatenated with a single
.
character.For example, valid names are:
foo bar foobar123 _foo_bar_123 foo.bar.baz
-
__m_parent__
¶ A reference to
IMock
object that is a parent of this mock.If mock has no parent (i.e. if it’s a root mock), then this should return
None
.
-
__m_session__
¶ Instance of
ISession
to be used by this mock.Value returned by this property MUST meet following condition:
if self.__m_parent__ is not None: assert self.__m_session__ is self.__m_parent__.__m_session__
In other words, if this mock has a parent, than it MUST be attached to same session object as its parent.
-
mockify.api - An all-in-one import module¶
A proxy module providing access to all publicly available classes and functions.
This module automatically imports public names from all other Mockify’s modules, so any needed class or function can be imported like this:
from mockify.api import satisfied, Mock, Return
See the list of available names below.
Rationale behind this module is that testing frameworks like PyTest provide access to all public names basically via single import, so test helper like Mockify should also provide similar behaviour. On the other hand, using a root module to do such imports is discouraged, as it would always import everything - even if the user does not want to.
Note
Since this is a proxy module, any change to other public modules will
affect this one, so f.e. removing a class from mockify.mock
module will
also remove it from mockify.api
module.
Currently available classes and functions are:
mockify.actions
mockify.actions.Action
mockify.actions.Return
mockify.actions.ReturnAsync
mockify.actions.ReturnContext
mockify.actions.ReturnAsyncContext
mockify.actions.Iterate
mockify.actions.IterateAsync
mockify.actions.YieldAsync
mockify.actions.Raise
mockify.actions.RaiseAsync
mockify.actions.Invoke
mockify.actions.InvokeAsync
New in version 0.13.
Changelog¶
0.13.1 (2021-09-20)¶
Fixed
- Fixed issue #37 (dictionary changed size during iteration in
ABCMock
when used with no expectations set)
0.13.0 (2021-09-10)¶
Added
- Introducing
mockify.abc
module - a set of ABC classes for Mockify- Introducing
mockify.api
module - a proxy for doing single-line imports of Mockify classes and functions- Added
mockify.mock.BaseFunctionMock
for making function mocks with user-defined fixed set of parameters- Added support for mocking magic methods in
mockify.mock.Mock
class- Added max_depth parameter to
mockify.mock.Mock
class constructor, so it can have limited depth of method namespacing (by default, the depth is unlimited)
Changed
- Class
mockify.mock.FunctionMock
now inherits frommockify.mock.BaseFunctionMock
- Class
mockify.core.BaseMock
was moved tomockify.mock.BaseMock
(old location is marked as deprecated)
Deprecated
- Current core module
mockify
is now made deprecated, so using names from that module will cause deprecation warnings. Please usemockify.core
instead.- Class
mockify.core.LocationInfo
is made deprecated and will be removed soon
0.12.0 (2020-11-29)¶
Added
- New action added:
mockify.actions.YieldAsync
- New action added:
mockify.actions.ReturnContext
- New action added:
mockify.actions.ReturnAsyncContext
0.11.0 (2020-11-24)¶
Added
- New action added:
mockify.actions.ReturnAsync
- New action added:
mockify.actions.IterateAsync
- New action added:
mockify.actions.RaiseAsync
- New action added:
mockify.actions.InvokeAsync
Changed
- Abstract method
mockify.actions.Action.format_params()
was removed andmockify.actions.Action.__str__()
is now made abstract instead- Abstract method
mockify.cardinality.ExpectedCallCount.format_params()
was removed andmockify.cardinality.ExpectedCallCount.__repr__()
is now made abstract instead
Deprecated
- Methods
mockify.core.BaseMock.__m_fullname__()
andmockify.core.BaseMock.__m_walk__()
are made deprecated and will be removed in one of upcoming releases; functionality is now provided completely by classmockify.core.MockInfo
(which was previously acting as a proxy)
Other
- Added CLI tasks to serve documentation and coverage locally for development purposes
0.10.0 (2020-11-13)¶
Added
- Added support for Python 3.9
Other
- Using
tox
to run tests against supported Python versions- Improved packaging according to https://github.com/pypa/sampleproject example project
0.9.1 (2020-11-09)¶
Other
- Added job to publish coverage reports to https://codecov.io/gl/zef1r/mockify
- Using
-pe
as default mode toinvoke
task runner (with help of config file)- Since now, tags are verified by CI before publishing to any PyPI, so it will not be possible to publish to test PyPI and to not publish to production PyPI (or vice-versa)
0.9.0 (2020-11-08)¶
Added
Added
mockify.core
module to replace importing of core stuff directly frommockify
root module.So instead of doing this:
from mockify import satisfiedIt is recommended to do this:
from mockify.core import satisfiedThis was changed because importing things directly from root module is discouraged, as it leads to longer import times.
Deprecated
Importing core parts of library directly from
mockify
core module is now deprecated - usemockify.core
instead.Since one of upcoming non-fix releases importing core parts of library from
mockify
core module will not work, unless you will use this:from mockify import core
Fixed
- Fixed some
pylint
bugs
Other
- Changelog was reformatted and split into sections in accordance to https://keepachangelog.com/en/1.0.0/
- Added tools for code formatting
- Added
pylint
linter- Small refactoring of project’s development tools
0.8.1 (2020-08-17)¶
Fixed
- Small fix in
mockify.matchers.Object
class to make it work whenmockify.matchers.Any
matcher is used as its argument and always inequal object is used when comparing
0.8.0 (2020-08-08)¶
Added
Added
mockify.core.BaseMock
that acts as common abstract base class for all mocks.Already existing classes
mockify.mock.Mock
andmockify.mock.MockFactory
now inherit from it.Added
mockify.mock.FunctionMock
for mocking Python functions and to be used internally when implementing complex mock classesAdded
mockify.mock.ABCMock
for implementing interfaces defined with help ofabc
module
0.7.1 (2020-06-17)¶
Fixed
- Fix
mockify.matchers.Object
matcher to be inequal to reference object if reference object does not have one or more properties listed in matcher
0.7.0 (2020-06-17)¶
Fixed
- An alias to 0.6.5 to fix versioning (new feature was introduced, and wrong version part was increased by mistake)
0.6.4 (2020-02-26)¶
Added
- New actions introduced (see
mockify.actions
)- New matchers introduced (see
mockify.matchers
)- New assertion errors introduced and improved exception hierarchy (see
mockify.exc
)- Can now define ordered expectations with
mockify.core.ordered()
context manager- Can now patch imports using
mockify.core.patched()
context manager
Changed
- Deprecated code was removed
- Class Registry was renamed to
mockify.core.Session
- All classes for making mocks were replaced by single generic
mockify.mock.Mock
class, supported bymockify.mock.MockFactory
class
Fixed
- Better reporting of expectation location in assertion messages
Other
- Improved documentation
- Documentation is now tested by Sphinx
- CI workflow updated + added testing against various Python versions (3.x for now)
- Many other improvements in the code and the tests
0.5.0 (2019-07-27)¶
Added
- Added
mockify.mock.Namespace
mock class
Changed
- Class
mockify.mock.Object
can now be used without subclassing and has API similar to other mock classes- Module mockify.helpers was merged to library core
- Module mockify.times was renamed to
mockify.cardinality
- Module mockify.engine is now available via
mockify
- Modules mockify.mock.function and mockify.mock.object are now merged into
mockify.mock
Other
- Dependency management provided by pipenv
- Project’s CLI provided by Invoke library
- Use Sphinx Read The Docs theme for documentation
0.2.1 (2019-01-05)¶
Added
- Added FunctionFactory mocking utility
Changed
- Changed Registry.assert_satisfied method to allow it to get mock names to check using positional args
Other
- Updated copyright notice
- Added description to Alabaster Sphinx theme used for docs
- Script for running tests added (pytest wrapper)
- Updated copyright.py script and hardcode year the project was started and author’s name
0.1.12 (2019-01-01)¶
- First release published to PyPI
License¶
Mockify is released under the terms of the MIT license.
Copyright (C) 2019 - 2021 Maciej Wiatrzyk <maciej.wiatrzyk@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.