Skip to main content

Mock object framework

Project description

Powerful and intuitive mock object framework for Python.

package-version python-versions Documentation Status

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/.

http://pymox.readthedocs.io/en/latest/index.html

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pymox-1.1.0.tar.gz (25.7 kB view hashes)

Uploaded Source

Built Distribution

pymox-1.1.0-py3-none-any.whl (29.7 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page