1

I am mocking out a method of a class and want to test the instance of the class that the method was called from to test that the creation part of my function works as expected.

In my particular case do_stuff tries to write bar_instance to an Excel File and I don't want that to happen i.e.

def create_instance(*args):
 return Bar(*args)

class Bar():
 def __init__(self, *args):
  self.args = args
 def do_stuff(self):
  pass

def foo(*args):
 bar_instance = create_instance(*args)
 bar_instance.do_stuff()

Then in a testing file

from unittest import TestCase
from unittest.mock import patch
from path.to.file import foo

class TestFoo(TestCase):
 @patch('path.to.file.Bar.do_stuff')
 def test_foo(self, mock_do_stuff):
  test_args = [1]
  _ = foo(*test_args)
  # Test here the instance of `Bar` that `mock_do_stuff` was called from
  # Something like
  actual_args = list(bar_instance.args)
  self.assertEqual(test_args, actual_args)

I put a break in the test function after foo(*test_args) is run but can't see any way from the mocked method of accessing the instance of Bar it was called from and am a bit stuck. I don't want to mock out Bar further up the code as I want to make sure the correct instance of Bar is being created.

2 Answers 2

2

In your code example, there are three things that might need testing: function create_instance, class Bar and function foo. I understand your test code such that you want to ensure that function foo calls do_stuff on the instance returned by create_instance.

Since the original create_instance function has control over the created instance, a solution of your problem is to mock create_instance such that your test gains control of the object that is handed over to foo:

import unittest
from unittest import TestCase
from unittest.mock import patch, MagicMock
from SO_60624698 import foo

class TestFoo(TestCase):
   @patch('SO_60624698.create_instance')
   def test_foo_calls_do_stuff_on_proper_instance (
         self, create_instance_mock ):
      # Setup
      Bar_mock = MagicMock()
      create_instance_mock.return_value = Bar_mock
      # Exercise
      foo(1, 2, 3) # args are irrelevant
      # Verify
      Bar_mock.do_stuff.assert_called()

if __name__ == '__main__':
   unittest.main()

In addition, you might also want to test if foo passes the arguments correctly to create_instance. This could be implemented as a separate test:

...
   @patch('SO_60624698.create_instance')
   def test_foo_passes_arguments_to_create_instance (
         self, create_instance_mock ):
      # Setup
      create_instance_mock.return_value = MagicMock()
      # Exercise
      foo(1, 22, 333)
      # Verify
      create_instance_mock.assert_called_with(1, 22, 333)

And, certainly, to complete the whole test of the object generation, you could test create_instance directly, by calling it and checking on the returned instance of Bar if it has used its arguments correctly for the construction of the Bar instance.

Sign up to request clarification or add additional context in comments.

1 Comment

This is a good solution. Seems like the key here is that I was trying to do too much in one unittest. And so separating them out allows me to mock each item separately. I still feel a bit like I'm missing a more integration style test, which allows us just to call foo and see what do_stuff is called on, without mocking the creation part out, but maybe that's just not really possible to back step from the mocked method
0

As patch returns an instance of Mock (or actually MagicMock, but it inherits the relevant methods from its base - Mock), you have the assert_called_with method available, which should do the trick. Note that this method is sensitive to args/kwargs - you have to assert the exact same call.

Another note: it might be a better practice to use patch.object instead of patch here

1 Comment

Thanks but this doesn't actually work. assert_called_with will only have access to the args that do_stuff was called with, in this case nothing, as opposed to the instance of Bar that do_stuff was called from, i.e. bar_instance

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.