Python Mock object with a method called multiple times

I have a class that I'm testing that has a dependency on another class (an instance of which is passed to the CUT init method). I want to mock this class using the Python Mock library.

I have something like:

mockobj = Mock(spec=MyDependencyClass) mockobj.methodfromdepclass.return_value = "the value I want the mock to return" assertTrue(mockobj.methodfromdepclass(42), "the value I want the mock to return") cutobj = ClassUnderTest(mockobj) 

This is good, but the methodfromdepclass method is a parameterized method, and as such I want to create one mock object, in which, depending on what arguments are passed to the method fromdempclass, it returns different values.

I want this parameterized behavior to be that I want to create several instances of ClassUnderTest that contain different values ​​(whose values ​​are created using what is returned from mockobj).

What I think (this of course does not work):

 mockobj = Mock(spec=MyDependencyClass) mockobj.methodfromdepclass.ifcalledwith(42).return_value = "you called me with arg 42" mockobj.methodfromdepclass.ifcalledwith(99).return_value = "you called me with arg 99" assertTrue(mockobj.methodfromdepclass(42), "you called me with arg 42") assertTrue(mockobj.methodfromdepclass(99), "you called me with arg 99") cutinst1 = ClassUnderTest(mockobj, 42) cutinst2 = ClassUnderTest(mockobj, 99) # now cutinst1 & cutinst2 contain different values 

How to achieve this "semantics" using "tailored"?

+46
python unit-testing mocking python-mock
Oct 05 '11 at 18:09
source share
4 answers

Try side_effect

 def my_side_effect(*args, **kwargs): if args[0] == 42: return "Called with 42" elif args[0] == 43: return "Called with 43" elif kwarg['foo'] == 7: return "Foo is seven" mockobj.mockmethod.side_effect = my_side_effect 
+71
Oct 05 '11 at 18:15
source share

A bit sweeter:

 mockobj.method.side_effect = lambda x: {123: 100, 234: 10000}[x] 

or for several arguments:

 mockobj.method.side_effect = lambda *x: {(123, 234): 100, (234, 345): 10000}[x] 

or with default value:

 mockobj.method.side_effect = lambda x: {123: 100, 234: 10000}.get(x, 20000) 

or a combination of both:

 mockobj.method.side_effect = lambda *x: {(123, 234): 100, (234, 345): 10000}.get(x, 20000) 

and fun at high speed.

+47
Nov 29 '12 at 23:04
source share

I came across this when I was conducting my own testing. If you don’t need to capture methodfromdepclass () method calls, but just want it to return something, the following might be enough:

 def makeFakeMethod(mapping={}): def fakeMethod(inputParam): return mapping[inputParam] if inputParam in mapping else MagicMock() return fakeMethod mapping = {42:"Called with 42", 59:"Called with 59"} mockobj.methodfromdepclass = makeFakeMethod(mapping) 

Here's the parameterized version:

 def makeFakeMethod(): def fakeMethod(param): return "Called with " + str(param) return fakeMethod 
+10
Jun 03 2018-12-12T00:
source share

As here , in addition to using side_effect in unittest.mock.Mock, you can also use @mock.patch.object with new_callable , which allows you to associate an object attribute with a dummy object.

Suppose the module my_module.py uses pandas to read from the database, and we would like to test this module with the pd.read_sql_table method (which takes the table_name argument).

What you can do is create (inside your test) a db_mock method that returns different objects depending on the argument provided:

 def db_mock(**kwargs): if kwargs['table_name'] == 'table_1': # return some DataFrame elif kwargs['table_name'] == 'table_2': # return some other DataFrame 

In your test function, you then do:

 import my_module as my_module_imported @mock.patch.object(my_module_imported.pd, "read_sql_table", new_callable=lambda: db_mock) def test_my_module(mock_read_sql_table): # You can now test any methods from 'my_module', eg 'foo' and any call this # method does to 'read_sql_table' will be mocked by 'db_mock', eg ret = my_module_imported.foo(table_name='table_1') # 'ret' is some DataFrame returned by 'db_mock' 
0
Jun 13 '19 at 22:54
source share



All Articles