Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I assert calls that accept sequence arguments with Python Mock?

I am processing a sequence of user-defined objects. It looks similar to the following:

class Thing(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

The method I am currently testing has functionality similar to the following:

def my_function(things):
    x_calc = calculate_something(t.x for t in things)
    y_calc = calculate_something(t.y for t in things)
    return x_calc / y_calc

The problem I am facing is testing the calls to calculate_something. I want to assert that these calls happened, something like so:

calculateSomethingMock.assert_any_call(the_sequence)

I don't care about the order of the sequence passed into calculate_something, but I do care that the elements are all present. I could wrap the generator function in a call to set, but I don't feel like my test should be dictating what type of sequence is passed into calculate_something. I should be able to pass it any kind of sequence. I could alternatively create a method that generates the sequence instead of using generator syntax and mock that method, but that seems like overkill.

How can I best structure this assertion, or is my trouble testing here an indication of poorly structured code?

I am using Python 2.7.3 with Mock 1.0.1.

(For anyone who feels compelled to comment on it, I'm aware I'm doing test last and that this isn't considered the greatest practice.)

Edit:

After watch this marvelous talk entitled "Why You Don't Get Mock Objects by Gregory Moeck", I have reconsidered whether I should even be mocking the calculate_something method.

like image 247
jpmc26 Avatar asked Jan 22 '13 17:01

jpmc26


2 Answers

Looking at the Mock documentation, there is a call_args_list that will do what you want.

So you will Mock out calculate_something on your test.

calculate_something = Mock(return_value=None)

After you my_function has finished you can check the arguments passed by doing:

calculate_something.call_args_list

which will return a list of all the calls made to it (with the corresponding elements passed).

Edit:

(Sorry it took me so long, I had to install Python3.3 on my machine)

mymodule.py

class Thing:
    ...
def calculate_something:
    ...

def my_function(things):
    # Create the list outside in order to avoid a generator object
    # from being passed to the Mock object.

    xs = [t.x for t in things]
    x_calc = calculate_something(xs)

    ys = [t.y for t in things]
    y_calc = calculate_something(ys)
    return True

test_file.py

import unittest
from unittest.mock import patch, call
import mymodule



class TestFoo(unittest.TestCase):

    # You can patch calculate_something here or
    # do so inside the test body with 
    # mymodule.calcualte_something = Mock()
    @patch('mymodule.calculate_something')
    def test_mock(self, mock_calculate):

        things = [mymodule.Thing(3, 4), mymodule.Thing(7, 8)]

        mymodule.my_function(things)

        # call_args_list returns [call([3, 7]), call([4, 8])]
        callresult = mock_calculate.call_args_list


        # Create our own call() objects to compare against
        xargs = call([3, 7])
        yargs = call([4, 8])

        self.assertEqual(callresult, [xargs, yargs])

        # or
        # we can extract the call() info
        # http://www.voidspace.org.uk/python/mock/helpers.html#mock.call.call_list
        xargs, _ = callresult[0]
        yargs, _ = callresult[1]

        xexpected = [3, 7]
        yexpected = [4, 8]

        self.assertEqual(xargs[0], xexpected)
        self.assertEqual(yargs[0], yexpected)

if __name__ == '__main__':
    unittest.main()
like image 83
Carlos Avatar answered Nov 07 '22 00:11

Carlos


I haven't touched the code I was originally working with for a good while, but I've been reconsidering my approach to testing in general. I've been trying to be more careful about what I do and don't mock. I recently realized that I was unconsciously starting to following this rule of thumb: mock something if it makes my test shorter and simpler and leave it alone if it makes the test more complicated. Simple input/output testing suffices in the case of this method. There are no external dependencies like a database or files. So in short, I think the answer to my question is, "I shouldn't mock calculate_something." Doing so makes my test harder to read and maintain.

like image 24
jpmc26 Avatar answered Nov 07 '22 01:11

jpmc26