Explain the difference between stubs, mocks, and spies in unit testing, and provide a concrete example of when to use each.
In unit testing, stubs, mocks, and spies are test doubles used to isolate the unit of code being tested from its dependencies. They each serve distinct purposes: 1. Stubs: A stub provides canned answers to calls made during the test. They are used to replace dependencies with controlled inputs, allowing you to test different scenarios in your unit of code. Stubs are primarily concerned with *state verification*, ensuring that the unit under test behaves correctly based on different input states. Example: Imagine you are testing a function that retrieves a user's profile from a database. You can use a stub to replace the database access layer. The stub will return a predefined user profile, allowing you to test how the function handles different profile scenarios (e.g., a user with a premium account vs. a user with a basic account) without actually connecting to the database. 2. Mocks: A mock is a programmable object that is pre-programmed with expectations which form a specification of the calls it is expected to receive. They are used to verify that the unit of code interacts with its dependencies in the expected way. Mocks are primarily concerned with *behavior verification*, ensuring that the unit under test calls the correct methods on its dependencies with the correct arguments. Example: Suppose you're testing a function that sends an email notification to a user. You can use a mock to replace the email sending service. The mock will be programmed to expect a call to its `sendEmail()` method with specific arguments (e.g., the user's email address and the notification message). The test will then verify that the `sendEmail()` method was called with the expected arguments, ensuring that the function correctly interacts with the email sending service. 3. Spies: A spy is a test double that records information about how it was used. They are similar to mocks but are less strict about expectations. Spies allow you to inspect the calls made to a dependency without pre-programming specific expectations. They are useful when you need to verify that a method was called, how many times it was called, or with what arguments, but you don't want to enforce strict expectations like mocks do. Example: Consider a function that logs user activity to a logging service. You can use a spy to replace the logging service. The spy will record all calls made to its `log()` method. The test can then verify that the `log()` method was called with the correct message and severity level, ensuring that the function is properly logging user activity. In summary: - Stubs provide controlled inputs to the unit under test. - Mocks verify that the unit under test interacts with its dependencies in the expected way. - Spies record information about how a dependency was used.