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.0.2.tar.gz (24.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pymox-1.0.2-py3-none-any.whl (27.6 kB view details)

Uploaded Python 3

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

Hashes for pymox-1.0.2.tar.gz
Algorithm Hash digest
SHA256 8debb322fbdf58dcad8f6ddc07205692a50489f13a575d7c28f5ebc709c24f2b
MD5 60a20b43df073c3d32dcacafa13a8960
BLAKE2b-256 3e85f62f69516064df23418365e8846d534eb05848f8f157bfae439c0ab31b56

See more details on using hashes here.

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

Hashes for pymox-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 8620517f1176c0695e6a8bf314b11cf6a57e087c12c11d31f856002eb5754086
MD5 55636953df0dd5a7d5732aa0fd2ad135
BLAKE2b-256 69ec3189df55f31e370f6cf0bc20ac2ad5bd24191912f8c1a1b3745ae28e1ea1

See more details on using hashes here.

Supported by

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