Codebase list python-mockito / 58bd4f4 docs / walk-through.rst
58bd4f4

Tree @58bd4f4 (Download .tar.gz)

walk-through.rst @58bd4f4view markup · raw · history · blame

The Walk-through

The 90% use case is that want to stub out a side effect. This is also known as (monkey-)patching. With mockito, it's:

from mockito import when

# stub `os.path.exists`
when(os.path).exists('/foo').thenReturn(True)

os.path.exists('/foo')  # => True
os.path.exists('/bar')  # -> throws unexpected invocation

So in difference to traditional patching, in mockito you always specify concrete arguments (a call signature), and its outcome, usually a return value via thenReturn or a raised exception via thenRaise. That effectively turns function calls into constants for the time of the test.

Do not forget to :func:`unstub` of course!

System Message: INFO/1 (<string>, line 16)

No role entry for "func" in module "docutils.parsers.rst.languages.en". Trying "func" as canonical role name.

System Message: ERROR/3 (<string>, line 16); backlink

Unknown interpreted text role "func".
from mockito import unstub
unstub()  # restore os.path module

Now we mix global module patching with mocks. We want to test the following function using the fab requests library:

import requests

def get_text(url):
    res = requests.get(url)
    if 200 <= res.status_code < 300:
        return res.text
    return None

How, dare, we did not inject our dependencies! Obviously we can get over that by patching at the module level like before:

when(requests).get('https://example.com/api').thenReturn(...)

But what should we return? We know it's a requests.Response object, (Actually I know this bc I typed this in the ipython REPL first.) But how to construct such a Response, its __init__ doesn't even take any arguments?

Should we actually use a 'real' response object? No, we fake it using :func:`mock`.

System Message: INFO/1 (<string>, line 40)

No role entry for "func" in module "docutils.parsers.rst.languages.en". Trying "func" as canonical role name.

System Message: ERROR/3 (<string>, line 40); backlink

Unknown interpreted text role "func".
# setup
response = mock({
    'status_code': 200,
    'text': 'Ok'
}, spec=requests.Response)
when(requests).get('https://example.com/api').thenReturn(response)

# run
assert get_text('https://example.com/api') == 'Ok'

# done!

Say you want to mock the class Dog:

class Dog(object):
    def bark(self):
        return 'Wuff'


# either mock the class
when(Dog).bark().thenReturn('Miau!')
# now all instances have a different behavior
rex = Dog()
assert rex.bark() == 'Miau!'

# or mock a concrete instance
when(rex).bark().thenReturn('Grrrr')
assert rex.bark() == 'Grrrr'
# a different dog will still 'Miau!'
assert Dog().bark() == 'Miau!'

# be sure to call unstub() once in while
unstub()

Sure, you can verify your interactions:

from mockito import verify
# once again
rex = Dog()
when(rex).bark().thenReturn('Grrrr')

rex.bark()
rex.bark()

# `times` defaults to 1
verify(rex, times=2).bark()

In general mockito is very picky:

# this will fail because `Dog` has no method named `waggle`
when(rex).waggle().thenReturn('Nope')
# this will fail because `bark` does not take any arguments
when(rex).bark('Grrr').thenReturn('Nope')


# given this function
def bark(sound, post='!'):
    return sound + post

from mockito import kwargs
when(main).bark('Grrr', **kwargs).thenReturn('Nope')

# now this one will fail
bark('Grrr')  # because there are no keyword arguments used
# this one will fail because `then` does not match the function signature
bark('Grrr', then='!!')
# this one will go
bark('Grrr', post='?')

# there is also an args matcher
def add_tasks(*tasks, verbose=False):
    pass

from mockito import args
# If you omit the `thenReturn` it will just return `None`
when(main).add_tasks(*args)

add_tasks('task1', 'task2')  # will go
add_tasks()  # will fail
add_tasks('task1', verbose=True)  # will fail too

# On Python 3 you can also use `...`
when(main).add_tasks(...)
# when(main).add_tasks(Ellipsis) on Python 2

add_tasks('task1')  # will go
add_tasks(verbose=True)  # will go
add_tasks('task1', verbose=True)  # will go
add_tasks()  # will go

To start with an empty stub use :func:`mock`:

System Message: INFO/1 (<string>, line 137)

No role entry for "func" in module "docutils.parsers.rst.languages.en". Trying "func" as canonical role name.

System Message: ERROR/3 (<string>, line 137); backlink

Unknown interpreted text role "func".
from mockito import mock

obj = mock()

# pass it around, eventually it will be used
obj.say('Hi')

# back in the tests, verify the interactions
verify(obj).say('Hi')

# by default all invoked methods take any arguments and return None
# you can configure your expected method calls with the ususal `when`
when(obj).say('Hi').thenReturn('Ho')

# There is also a shortcut to set some attributes
obj = mock({
    'hi': 'ho'
})

assert obj.hi == 'ho'

# This would work for methods as well; in this case
obj = mock({
    'say': lambda _: 'Ho'
})

# But you don't have any argument and signature matching
assert obj.say('Anything') == 'Ho'

# At least you can verify your calls
verify(obj).say(...)

# Btw, you can make screaming strict mocks::
obj = mock(strict=True)  # every unconfigured, unexpected call will raise

You can use an empty stub specced against a concrete class:

# Given the above `Dog`
rex = mock(Dog)

# Now you can stub out any known method on `Dog` but other will throw
when(rex).bark().thenReturn('Miau')
# this one will fail
when(rex).waggle()

# These mocks are in general very strict, so even this will fail
rex.health  # unconfigured attribute

# Of course you can just set it in a setup routine
rex.health = 121

# Or again preconfigure
rex = mock({'health': 121}, spec=Dog)

# preconfigure stubbed method
rex = mock({'bark': lambda sound: 'Miau'}, spec=Dog)

# as you specced the mock, you get at least function signature matching
# `bark` does not take any arguments so
rex.bark('sound')  # will throw TypeError

# Btw, you can make loose specced mocks::
rex = mock(Dog, strict=False)