Welcome to our third lesson on Test-Driven Development (TDD) with Test Doubles. In this lesson, we focus on Spies, an essential type of test double used to observe interactions with your code's dependencies during testing. By now, you've already been introduced to Dummies and Stubs in previous lessons, which allow you to manage dependencies via test doubles effectively.
Our goal here is to seamlessly integrate Spies into the TDD Red-Green-Refactor cycle: writing tests (Red), creating minimal implementations (Green), and refactoring for better quality without altering behavior. Let's dive into understanding and using Spies within this framework using Python's pytest-mock.
Spies are a powerful tool available through Python's pytest-mock module. They allow you to observe and record how functions in your application are used without modifying their behavior. In TDD, Spies help verify that functions are called when and how you expect them to be, which is a vital part of writing reliable tests.
Spies can check:
They align perfectly with the Red-Green-Refactor cycle:
Let's set up our environment using pytest-mock to harness this powerful tool.
Let's consider a Notification Service example where we aim to ensure notifications are sent with appropriate priorities. We will begin by implementing a Spy on the send method of the RealNotificationSender. This allows you to use an actual dependency within your test and verify how it was called.
Here's an example test file: test_notification_sender.py.
mocker.spy on the send method of RealNotificationSender.send method.Next, we insert failing tests to see our Spies in action. Remember that writing failing tests is our "Red" step in TDD.
Let's add some test cases to observe the behavior of the send method using our Python Spy.
send method.Upon running this test, it might fail initially as we have not written the corresponding implementation. This is the Red step of our cycle. Implementing minimal logic in the NotificationService class will move us towards making the test pass (Green).
After writing a failing test to capture how our send method should behave, the next step is to implement the minimal functionality required to make the test pass. This involves modifying the NotificationService class to utilize the send method correctly based on the specified logic.
Here's an example of updating the NotificationService to fulfill the test requirements:
send method is then invoked with the user_id, message, and calculated priority ('low' for now).With this implementation, re-running the test should now pass, taking us from the Red to the Green phase of TDD.
In the example above, you see how we can assert that function being spied on is called. But what if you have multiple calls to the spied function that you want to verify? That's easy with the assert_has_calls([]) function on the spy:
send method has been called three times in our test scenario.assert_has_calls method is used to verify that the send function was called with specific arguments for multiple instances.assert_has_calls contains the expected call details for each invocation, preserving the order of calls when any_order=False.In this lesson, we've explored the importance of Spies as test doubles in the TDD process using Python and Pytest with pytest-mock. Here's what we've achieved:
As we transition into practice exercises, focus on implementing Spies in different scenarios to solidify these concepts. The exercises will guide you in writing, verifying, and refactoring tests with Spies, empowering you to integrate this knowledge into complex projects.
Happy testing, and enjoy the lessons you've built through TDD in Python with pytest-mock!
