Mock object framework
Project description
Powerful and intuitive mock object framework for Python.
Install
pip install pymox
Tutorial
Basics
Pymox works in a way you set expectations and then enter in replay mode. Here is a basic example:
class Duck:
def quack(self, times=1):
return ['quack'] * times
def walk(self):
return ['walking']
def walk_and_quack(self, times=1):
return self.walk() + self.quack(times=times)
Here is a Duck class. Let’s play with our 🦆 and Pymox!
import mox
class TestDuck:
def test_quack(self):
m = mox.Mox()
m_duck = m.CreateMock(Duck)
# expects quack to be called with `times=1`
m_duck.quack(times=1).returns(['new quack'])
m.replay_all()
assert m_duck.quack(times=1) == ['new quack']
m.verify_all()
Let’s change the test a little bit:
# [...]
def test_quack_2(self):
m = mox.Mox()
m_duck = m.CreateMock(Duck)
# expects quack to be called with `times=1`
m_duck.quack(times=1).returns(['new quack'])
m.replay_all()
assert m_duck.walk() == ['walking']
assert m_duck.quack(times=1) == ['new quack']
m.verify_all()
The test above will fail with the following error:
E mox.mox.UnexpectedMethodCallError: Unexpected method call. unexpected:- expected:+
E - Duck.walk() -> None
E + Duck.quack(times=1) -> ['new quack']
Since you expected quack to be called and walk was called instead. You can add an expectation for walk:
def test_quack_3(self):
m = mox.Mox()
m_duck = m.CreateMock(Duck)
# expects quack to be called with `times=1`
m_duck.quack(times=1).returns(['new quack'])
m_duck.walk().returns(['pretending to be walking'])
m.replay_all()
assert m_duck.quack(times=1) == ['new quack']
assert m_duck.walk() == ['pretending to be walking']
m.verify_all()
You can also stub out quack method only and mox won’t care about the other methods:
def test_quack_4(self):
m = mox.Mox()
duck = Duck()
m.stubout(duck, 'quack')
"""
You can also do with the class:
m.stubout(Duck, 'quack')
"""
# expects quack to be called with `times=1`
duck.quack(times=1).returns(['new quack'])
m.replay_all()
assert duck.quack(times=1) == ['new quack']
assert duck.walk() == ['walking']
m.verify_all()
The order matters, so if you do:
def test_quack_5(self):
m = mox.Mox()
m_duck = m.CreateMock(Duck)
# expects quack to be called with `times=1`
m_duck.quack(times=1).returns(['new quack'])
m_duck.walk().returns(['pretending to be walking'])
m.replay_all()
assert m_duck.walk() == ['pretending to be walking']
assert m_duck.quack(times=1) == ['new quack']
m.verify_all()
It fails with:
E mox.mox.UnexpectedMethodCallError: Unexpected method call. unexpected:- expected:+
E - Duck.walk() -> None
E + Duck.quack(times=1) -> ['new quack']
To fix that you can use any_order():
def test_quack_6(self):
m = mox.Mox()
m_duck = m.CreateMock(Duck)
# expects quack to be called with `times=1`
m_duck.quack(times=1).any_order().returns(['new quack'])
m_duck.walk().any_order().returns(['pretending to be walking'])
m.replay_all()
assert m_duck.walk() == ['pretending to be walking']
assert m_duck.quack(times=1) == ['new quack']
Comparators
You can use comparators when you are unsure of the arguments of a method call.
def test_quack_7(self):
m = mox.Mox()
duck = Duck()
m.stubout(Duck, 'quack')
def validate_arg(arg):
if arg in [1, 2, 3]:
return True
return False
duck.quack(times=mox.is_a(int)).returns(['new quack'])
duck.quack(times=mox.not_(mox.is_(4))).returns(['new quack'])
duck.quack(times=mox.func(validate_arg)).returns(['new quack'])
duck.quack(times=mox.or_(mox.Is(1), mox.is_(2), mox.is_(3))).returns(['new quack'])
duck.quack(times=mox.ignore_arg()).returns(['new quack'])
duck.quack(times=mox.is_almost(1.00003, places=4)).returns(['new quack'])
m.replay_all()
assert duck.quack(times=random.choice([1, 2, 3])) == ['new quack']
assert duck.quack(times=random.choice([1, 2, 3])) == mox.in_('new quack')
assert duck.quack(times=random.choice([1, 2, 3]))[0] == mox.str_contains('quack')
assert duck.quack(times=random.choice([1, 2, 3])) == mox.same_elements_as({'new quack'})
assert duck.quack(times=random.choice([1, 2, 3])) == ['new quack']
assert duck.quack(times=1) == ['new quack']
m.verify_all()
All the assertions for the test above should pass. There are other cool comparators, like: and, contains_attribute_value, contains_key_value.
For more comparators, see: https://pymox.readthedocs.io/en/latest/reference.html#comparators
Remember
It’s possible to also remember a value that might be changed in your code. See the test below:
def test_quack_8(self):
class StopQuackingDuck:
def _do_quack(self, choices=None):
return choices
def quack(self, choices=[], less=False):
if less:
choices.pop()
self._do_quack(choices=choices)
m = mox.Mox()
duck = StopQuackingDuck()
m.stubout(StopQuackingDuck, '_do_quack')
choices_1 = mox.value()
choices_2 = mox.value()
duck._do_quack(choices=mox.remember(choices_1))
duck._do_quack(choices=mox.remember(choices_2))
duck._do_quack(choices=mox.remember(choices_2))
duck._do_quack(choices=mox.remember(choices_2))
all_choices = ['quack', 'new quack', 'newest quack']
m.replay_all()
duck.quack(all_choices, less=False)
assert choices_1 == ['quack', 'new quack', 'newest quack']
duck.quack(all_choices, less=True)
assert choices_2 == ['quack', 'new quack']
duck.quack(all_choices, less=True)
assert choices_2 == ['quack']
duck.quack(all_choices, less=True)
assert choices_2 == []
m.verify_all()
Other
You can also make a method return a different value the second time it’s called:
def test_walk_and_quack_0(self):
m = mox.Mox()
duck = Duck()
m.stubout(Duck, 'quack')
duck.quack(times=1).returns(['new quack'])
duck.quack(times=1).returns(['newest quack'])
m.replay_all()
assert duck.walk_and_quack() == ['walking', 'new quack']
But since we didn’t use m.verify_all(), it didn’t require the second call to happen. Let’s add the verify and see what happens:
def test_walk_and_quack_1(self):
m = mox.Mox()
duck = Duck()
m.stubout(Duck, 'quack')
duck.quack(times=1).returns(['new quack'])
duck.quack(times=1).returns(['newest quack'])
m.replay_all()
assert duck.walk_and_quack() == ['walking', 'new quack']
m.verify_all()
It fails with:
E mox.mox.ExpectedMethodCallsError: Verify: Expected methods never called:
E 0. Duck.quack.__call__(times=1) -> ['newest quack']
Let’s fix it by adding a second call:
def test_walk_and_quack_2(self):
m = mox.Mox()
duck = Duck()
m.stubout(Duck, 'quack')
duck.quack(times=1).returns(['new quack'])
duck.quack(times=1).returns(['newest quack'])
m.replay_all()
assert duck.walk_and_quack() == ['walking', 'new quack']
assert duck.walk_and_quack() == ['walking', 'new quack']
m.verify_all()
Now you get the following error, since in the second time it returns [‘newest quack’].
E AssertionError: assert ['walking', 'newest quack'] == ['walking', 'new quack']
E At index 1 diff: 'newest quack' != 'new quack'
E Full diff:
E - ['walking', 'new quack']
E + ['walking', 'newest quack']
E ? +++
Let’s fix it:
def test_walk_and_quack_3(self):
m = mox.Mox()
duck = Duck()
m.stubout(Duck, 'quack')
duck.quack(times=1).returns(['new quack'])
duck.quack(times=1).returns(['newest quack'])
m.replay_all()
assert duck.walk_and_quack() == ['walking', 'new quack']
assert duck.walk_and_quack() == ['walking', 'newest quack']
m.verify_all()
Let’s now see how we can mock and assert calls in the context of a loop:
def test_walk_and_quack_4(self):
m = mox.Mox()
duck = Duck()
m.stubout(Duck, 'quack')
duck.quack(times=1).returns(['new quack'])
m.replay_all()
assert duck.walk() == ['walking']
for _ in range(3):
assert duck.walk_and_quack() == ['walking', 'new quack']
m.verify_all()
If you run the test above, you get the following:
E mox.mox.UnexpectedMethodCallError: Unexpected method call Duck.quack.__call__(times=1) -> None
Let’s fix by using the multiple_times group.
def test_walk_and_quack_5(self):
m = mox.Mox()
duck = Duck()
m.stubout(Duck, 'quack')
duck.quack(times=1).multiple_times().returns(['new quack'])
m.replay_all()
assert duck.walk() == ['walking']
for _ in range(3):
assert duck.walk_and_quack() == ['walking', 'new quack']
m.verify_all()
If you know exactly how many calls are made, you can add an argument: .multiple_times(3).
Next
That’s it for now! For a more comprehensive tutorial, see: https://pymox.readthedocs.io/en/latest/tutorial.html
For more examples, see: https://pymox.readthedocs.io/en/latest/examples.html
For the API reference, see: https://pymox.readthedocs.io/en/latest/reference.html
Documentation
For full documentation, including installation, tutorials and PDF documents, please see http://pymox.rtfd.io/.
Disclaimer
Pymox is a fork of Mox. Mox is Copyright 2008 Google Inc, and licensed under the Apache License, Version 2.0; see the file COPYING for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pymox-1.0.2.tar.gz.
File metadata
- Download URL: pymox-1.0.2.tar.gz
- Upload date:
- Size: 24.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8debb322fbdf58dcad8f6ddc07205692a50489f13a575d7c28f5ebc709c24f2b
|
|
| MD5 |
60a20b43df073c3d32dcacafa13a8960
|
|
| BLAKE2b-256 |
3e85f62f69516064df23418365e8846d534eb05848f8f157bfae439c0ab31b56
|
File details
Details for the file pymox-1.0.2-py3-none-any.whl.
File metadata
- Download URL: pymox-1.0.2-py3-none-any.whl
- Upload date:
- Size: 27.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8620517f1176c0695e6a8bf314b11cf6a57e087c12c11d31f856002eb5754086
|
|
| MD5 |
55636953df0dd5a7d5732aa0fd2ad135
|
|
| BLAKE2b-256 |
69ec3189df55f31e370f6cf0bc20ac2ad5bd24191912f8c1a1b3745ae28e1ea1
|