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()
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 theselect_menu_item
method of theTerminal_Menu
class withmock_select_menu_item
. - Now, when
get_destination
is called, it will usemock_select_menu_item
instead of the realselect_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.