Explaining the Fixtures in Pytest (vs Mocking)

The aim of this page📝 is to explain the concepts of fixtures and mocking in unit testing based on the particular example of a function get_destination() I have been written to simplify an app deployment.

Pavol Kutaj

--

In short, a fixture is a simulation of a frozen state (always the same output) required to assert the requested result — for example, you can create a fixture to achieve an output of a function by monkeypatching (jumping in the function call and returning pre-defined constant value without really executing a function). Or you could create a fixture to simulate the contents of a file to always return the same value for unit testing and therefore reliably test the code in question.

On the other hand, mocks are not simulated results but can be more dynamic, they are simulated objects that you define. Therefore mocks are more general/more complex than fixtures. I can mock a class, I can mock a method, I can mock a database.

  • Fixtures and mocking are two different concepts used in unit testing.
  • The purpose of a test fixture is to ensure that there is a well-known and fixed environment in which tests are run so that results are repeatable.
  • Examples of fixtures include loading a database with a specific, known set of data, copying a specific known set of files, preparation of input data, and setup/creation of fake or mock objects.
  • Mocking is primarily used in unit testing. An object under test may have dependencies on other (complex) objects.
  • To isolate the behavior of the object you want to test, you replace the other objects by mocks that simulate the behavior of the real objects.
  • Mocks are objects that simulate the behavior of real objects by mimicking their behavior and controlling their outputs.
  • This is useful if the real objects are impractical to incorporate into the unit test.
  • Fixtures set up a consistent test environment, while mocking is used to simulate and control the behavior of dependencies.
  • Both contribute to making tests more reliable and easier to maintain.

CODE

Here is a particular example I have experienced when learning about how to simulate a method call. In short, by navigating monkeypatch.setattr() fixture to a class. This is the code

def get_destination() -> tuple:
destination_per_cloud = Terminal_Menu(list_template_files())
print_header("Select destination")
destination_per_cloud.print_menu()
selection = destination_per_cloud.select_menu_item()
return selection

In this code, we want to use monkeypatch fixture for the method call selection = destination_per_cloud.select_menu_item() to return "kafka_template". Here's how you can do it (note that there is an element of mocking still involved!):

def test_get_destination(monkeypatch):
def mock_select_menu_item():
return "kafka_template"
monkeypatch.setattr(Terminal_Menu, "select_menu_item", mock_select_menu_item)
assert get_destination() == "kafka_template"

In this example, the mock_select_menu_item function is defined to return "kafka_template".

  • The monkeypatch.setattr function is then used to replace the select_menu_item method of the Terminal_Menu class with mock_select_menu_item.
  • Now, when get_destination is called, it will use mock_select_menu_item instead of the real select_menu_item method.

Fixtures are simple custom calls. With mocking you are constructing rather than just calling with simple lambdas. The language and tools are different and mocks are more challenging.

--

--

Pavol Kutaj

Today I Learnt | Infrastructure Support Engineer at snowplow.io with a passion for cloud infrastructure/terraform/python/docs. More at https://pavol.kutaj.com