-
Notifications
You must be signed in to change notification settings - Fork 18
Documentation
Stub: fake object that returns a canned value
Mock: fake object that returns a canned value and has an expectation, i.e. it includes a built-in assertion
Spy: watches method calls and records/verifies if the method is called with required parameters
FlexMock's automatic test runner integration allows you to write test helper methods that define your expectations and reuse them in different tests, possibly with different values. This is either completely impossible or very awkward to accomplish with other Python mocking libraries.
For example:
def _mock_server_connection(url, value):
flexmock(ServerConnection).should_receive('get').with_args(url).and_return(value).once
class TestStuff(unittest.TestCase):
def test_connection_to_server_a(self):
_mock_server_connection('http://server-a/status', 'OK')
run_the_code_that_will_try_connection_to_server_a()
def test_connection_to_server_b(self):
_mock_server_connection('http://server-b/status', 'BAD')
run_the_code_that_will_try_connection_to_server_b()
It's easy to write helper methods that define stubs in this way using almost any library out there. However, what's interesting above the above is the "once" modifier on the expectation. In addition to providing stub values for the specified method the _mock_server_connection() helper method also guarantees that the call to that method will be made with the specified parameters. That logic is completely taken care of by calling the helper method, freeing you from having to make any extra explicit assertions later in your tests.
FlexMock also supports a much terser and cleaner syntax than most Python mocking libraries out there. As a result, you can focus on what to test rather than trying to figure out how to coax the mocking library into doing what you need.
import unittest
from flexmock import flexmock
class SomeTest(unittest.TestCase):
def test_something(self):
...
mock = flexmock(method1=lambda: 'a', attr='b')
mock.should_receive('method2').and_return('c')
mock.should_receive('method3').and_raise(Exception)
mock.attr
>> 'b'
mock.method1()
>> 'a'
mock.method2()
>> 'c'
mock.method3()
>> Traceback (most recent call last):
>> File "<stdin>", line 1, in <module>
>> File "flexmock.py", line 336, in mock_method
>> raise expectation.exception
>> Exception
mock = flexmock(some_object)
mock.should_receive('method_bar').with_args('a').once
mock.should_receive('method_bar').with_args('b').at_least.twice
mock.should_receive('method_bar').with_args('c').at_most.once
mock.should_receive('method_bar').with_args('d').never
If any of these are not called the correct number of times an exception will be raised.
-- matching specific arguments --
flexmock(some_object).should_receive('method_bar').with_args(arg1, arg2).at_least.once.and_execute
-- matching any arguments --
flexmock(some_object).should_receive('method_bar').twice.and_execute
The real method_bar() will be called, but flexmock will verify that it's called the correct number of times and with the expected arguments.
class User: pass
user = User()
flexmock(user).should_receive('get_name').and_return('name')
class User: pass
flexmock(User)
User.should_receive('method_foo').and_return('value_bar')
user = User()
user.method_foo()
>> 'value_bar'
class Group: pass
group = Group()
flexmock(group)
# stub -- no assertions checked
group.should_receive('get_name').and_return('group_name')
# mock -- make an assertion that the method will get called one time with any args
group.should_receive('get_member').and_return([]).once
group.should_receive('get_member').and_return('user1').and_return('user2').and_return('user3')
group.get_member()
>> 'user1'
group.get_member()
>> 'user2'
group.get_member()
>> 'user3'
-- or use the short-hand form --
group.should_receive('get_member').and_return('user1, 'user2', 'user3').one_by_one
-- you can also mix return values with exception raises --
group.should_receive('get_member').and_return('user1').and_raise(Exception).and_return('user2')
class Group(object): pass
flexmock(Group, new_instances='foo')
Overriding new instances of old-style classes is currently not supported directly, you should make the class inherit from "object" in your code first. Luckily, multiple inheritance should make this pretty painless.
flexmock(foo).should_receive('gen').and_yield(1, 2, 3)
for i in foo.gen():
print i
>> 1
>> 2
>> 3
from flexmock import flexmock
As far as I can tell most test runners out there support unittest.TestCase so as long as your tests are in a class that inherits from unittest.TestCase flexmock will just work.
If you must have tests at module level or require hooking into more fine-grained setup/teardown functionality, you can check the flexmock_unittest() function to see how to implement integration with another test runner. It should be reasonably straight-forward -- you need to define flexmock_<test_runner>() that contains a class that inherits from FlexMock and implements update_teardown(). Something along the lines of:
def flexmock_<test_runner>(*kargs, **kwargs):
import <test_runner>
class UnittestFlexMock(FlexMock):
def update_teardown(self, test_runner=<test_runner>, teardown_method='tearDown'):
FlexMock.update_teardown(self, test_runner, teardown_method)
return UnittestFlexMock(*kargs, **kwargs)
-- then in your test code --
from flexmock import flexmock_<test_runner> as flexmock
And don't forget to send me the patch! :)
Creating an expectation with no arguments will by default match all arguments, including no arguments.
-- that is --
flexmock(foo).should_receive('method_bar').and_return('bar')
-- will be matched by --
foo.method_bar()
>> 'bar'
foo.method_bar('foo')
>> 'bar'
foo.method_bar('foo', 'bar')
>> 'bar'
In addition to exact values, you can also match against the type or class of the argument.
-- so --
flexmock(foo).should_receive('method_bar').with_args(object)
-- will match any single argument --
flexmock(foo).should_receive('method_bar').with_args(str)
-- will match any single string argument --
flexmock(foo).should_receive('method_bar').with_args(int, object, 'foo')
-- will match any set of three arguments where the first one is an integer, second one is anything, and third is string 'foo' --
Matching against user defined classes is also supported in the same fashion.
You can also override the default match with another expectation for the same method.
-- that is --
flexmock(foo).should_receive('method_bar').and_return('bar')
flexmock(foo).should_receive('method_bar').with_args('foo').and_return('foo')
-- so now --
foo.method_bar()
>> 'bar'
foo.method_bar('foo', 'bar')
>> 'bar'
-- but --
foo.method_bar('foo')
>> 'foo'
The order of the expectations being defined is significant, with later expectations having higher precedence than previous ones. Which means that if you reversed the order of the example expectations above the more specific expectation would never be matched.
You can take advantage of this fact by applying the "ordered" keyword which will make an out of order call raise and exception:
flexmock(foo).should_receive('method_bar').with_args('bar').and_return('bar').ordered
flexmock(foo).should_receive('method_bar').with_args('foo').and_return('foo').ordered
-- so calling the methods in the same order will be fine --
foo.method_bar('bar')
>> 'bar'
foo.method_bar('foo')
>> 'foo'
-- but trying to call the second one first will result in an exception --
foo.method_bar('foo')
>> Traceback (most recent call last):
>> File "<stdin>", line 1, in <module>
>> File "flexmock.py", line 449, in mock_method
>> expectation = self._get_flexmock_expectation(method, arguments)
>> File "flexmock.py", line 415, in _get_flexmock_expectation
>> self._verify_call_order(e, args, name)
>> File "flexmock.py", line 426, in _verify_call_order
>> FlexMock._format_args(exp.method, exp.args)))
>> flexmock.MethodCalledOutOfOrder: method_bar("foo") called before method_bar("bar")