1

Sometimes when I develop by TDD (Test Driven Development) I need to test the calling order of some class methods. In general I write Python code so I'll show the last test case that I have just written.

Production code

I have a class with 3 methods (in the file mount_manager.py):

class MountManager:
    def create_file(self):
        ...

    def format_file(self):
        ...

    def create_and_format(self):
        self.create_file()
        self.format_file()

The method create_and_format() is the code under test; in it the method create_file() must be called before the method format_file() (otherwise the calling of the method format_file() fail because the file to format doesn't exist).

Test code

With the TDD in mind, before to write the production code, I have written the test code test_create_and_format() with the goal to verify that the order of the methods called by create_and_format() would be correct:

import unittest
from unittest import mock

from mount_manager import MountManager

class MountManagerTest(unittest.TestCase):
    ..
    def test_create_and_format(self):
        mock_mount_manager = mock.create_autospec(MountManager)
        MountManager.create_and_format(mock_mount_manager)
        self.assertEqual('create_file', mock_mount_manager.method_calls[0][0])
        self.assertEqual('format_file', mock_mount_manager.method_calls[1][0])

I know this post which is near to mine, but it doesn't work with a class and its methods but with a module and its functions; this difference stops me to apply that method to write my test code.

Question

Because in the test I call the method under test, create_and_test(), from the class (see the instruction MountManager.create_and_format() that is a class attribute reference) and not from an instance of the class while in the production code I call the method from an instance of the class, I'm looking for a different way to write the test code.

Could someone suggest me an other way to write the test code test_create_and_format()

4
  • Couldn't this be tested by validating that the results of each method are successfully observed? That is, wouldn't this test just be a combination of the tests for each of the other methods individually? Currently it seems like you're trying to test an implementation detail of the class rather than the result of the functionality, no? Commented Oct 13 at 14:28
  • With TDD I have to write the test code before the production code; this implies that because I have to write a method which does only 2 calls to 2 methods in a precise order the only thing to test is that the order of the calling is right. Otherwise I have to write the method (create_and_format()) without a test and this is not compliant with TDD philosophy. Commented Oct 13 at 14:33
  • Don't mock parts of the thing you're supposedly testing. It should be pretty straightforward to test this without tying yourself in knots, because: what would happen if you formatted a file that didn't yet exist?! And an implementation that repeated either step wouldn't be the simplest possible code that passes the test. Commented Oct 13 at 16:36
  • Thanks. I'll think about your suggestion. Commented Oct 13 at 17:09

1 Answer 1

1

To test the call order on an instance you can use the attach_mock feature. Calls to an attached mock will be recorded in the method_calls and mock_calls attributes of the parent mock. Then, since they're recorded on the same mock, you can explicitly make an assertion on the call order.

import unittest
from unittest import mock

from mount_manager import MountManager

class MountManagerTest(unittest.TestCase):

    def test_create_and_format(self):
        mount_manager = MountManager()
        parent_mock = mock.MagicMock()
        with mock.patch.object(MountManager, "create_file") as mock_create:
            with mock.patch.object(MountManager, "format_file") as mock_format:
                parent_mock.attach_mock(mock_create, "creator")
                parent_mock.attach_mock(mock_format, "formatter")
                mount_manager.create_and_format()
        parent_mock.assert_has_calls([mock.call.creator(), mock.call.formatter()])

This test should pass, given the mount_manager.py shown in the question.

However, if the order of create_file and format_file are switched in the implementation (i.e. in mount_manager.py) then the test should fail with an explanation looking something like this:

FAILED test_mount_manager.py::MountManagerTest::test_create_and_format - AssertionError: Calls not found.
Expected: [call.creator(), call.formatter()]
  Actual: [call.formatter(), call.creator()]
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you very much. I have change my test code following your suggestion and it test correctly the order of the calling. I didn't know attach_mock. I'll study it for future uses.
Without your example the documentation of attach_mock is a bit cryptic.

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.