CHAPTER 7
Up until now, we’ve been concentrating on using FakeItEasy in the setup of our NUnit unit tests. We haven’t seen any assertions against any of our setups.
We’ve all written countless assertions using unit test frameworks. For NUnit, we can assert using Assert.That(x, Is.EqualTo(y)) where x and y are of the same type, plus the many other types of assertions available to us via the framework.
But how do we assert that something has happened on a configured fake?
NUnit owns the creation of the SUT, and we use FakeItEasy to provide the configured fakes to the SUT, but how do we assert something has happened or not happened in our unit tests after our setup is complete? FakeItEasy provides two operators for us to do just this: MustHaveHappened and MustNotHaveHappened. We’ll be exploring both of these methods in this chapter.
MustHaveHappened does exactly what it says. Based on fake’s setup and configuration, you can then assert that something was called on the fake using MustHaveHappened.
Let’s stick with our ISendEmail interface example, and make some changes to it to demonstrate how MustHaveHappened can be used:
Code Listing 68: The ISendEmail interface
We’ll also use an ICustomerRepository interface that has a GetAllCustomers method on it that returns a list of customers:
public interface ICustomerRepository { List<Customer> GetAllCustomers(); } |
Code Listing 69: The ICustomerRepository interface
Now, we’ll add a CustomerService class that allows us to send an email to all customers:
public class CustomerService { private readonly ISendEmail emailSender; private readonly ICustomerRepository customerRepository; public CustomerService(ISendEmail emailSender, ICustomerRepository customerRepository) { this.emailSender = emailSender; this.customerRepository = customerRepository; } public void SendEmailToAllCustomers() { var customers = customerRepository.GetAllCustomers(); foreach (var customer in customers) { emailSender.SendMail(); } } } |
Code Listing 70: The CustomerService class
As you can see from the class, we’re looping through the customers returned from GetAllCustomers, then calling SendMail for each customer. So far, nothing too complicated. We’ve already seen a couple examples like this when configuring calls to a fake and specifying a fake’s behavior. But this time, we’re going to assert that the SendMail method was called in our unit tests assertion. Here is the unit test code:
[TestFixture] public class WhenSendingEmailToAllCustomers { private ISendEmail emailSender; [SetUp] public void Given() { emailSender = A.Fake<ISendEmail>(); var customerRepository = A.Fake<ICustomerRepository>(); A.CallTo(() => customerRepository.GetAllCustomers()) .Returns(new List<Customer> { new Customer() }); var sut = new CustomerService(emailSender, customerRepository); sut.SendEmailToAllCustomers(); } [Test] public void SendsEmail() { A.CallTo(() => emailSender.SendMail()).MustHaveHappened(); } } |
Code Listing 71: Asserting that a call to the fake’s SendMail method must have happened
Looking at the unit test above, you can see in the SendsEmail test method that we are asserting that a call was made to the SendMail method on the fake using MustHaveHappened.
In looking at our unit test for CustomerService class again, it looks pretty simple. But we’re only asserting that the call happened. What if we want to assert that a configured fake was called more than once, or a certain number of times? We can do this by passing a FakeItEasy abstract class called Repeated to the MustHaveHappened method.
Here is the Repeated abstract class:
public abstract class Repeated { protected Repeated(); public static IRepeatSpecification AtLeast { get; } public static IRepeatSpecification Exactly { get; } public static Repeated Never { get; } public static IRepeatSpecification NoMoreThan { get; } public static Repeated Like(Expression<Func<int, bool>> repeatValidation); } |
Code Listing 72: The FakeItEasy Repeated abstract class
There is a good amount of functionality here. Let’s look at some examples to clarify.
Let’s change our unit test to demonstrate how to use MustHaveHappened with Repeated. For this example, the test setup remains the same; only the test method’s assertion changes:
[Test] public void SendsEmail() { A.CallTo(() => emailSender.SendMail()).MustHaveHappened(Repeated.Exactly.Once); } |
Code Listing 73: Testing that the SendMail was called once using Repeated
Note how we added Repeated.Exactly.Once as an argument to MustHaveHappened. Repeated is very useful here, as we want to make sure we’re not sending the same email more than once to the same customer. Where Repeated also helps is in asserting that calls to databases are made once, or, when performing inserts or updates in a loop, asserting that the call to the database was made a certain number of times.
Speaking of asserting that a call was made more than once, let’s change our unit test to reflect that. In our unit test setup, let’s change the customer repository fake to return two customers instead of one:
A.CallTo(() => customerRepository.GetAllCustomers()) .Returns(new List<Customer> { new Customer(), new Customer() }); |
Code Listing 74: Returning two customers instead of one
We’ll also update our unit test to assert that call to SendMail was made twice:
A.CallTo(() => emailSender.SendMail()) .MustHaveHappened(Repeated.Exactly.Twice); |
Code Listing 75: Assertion using Repeated.Exactly.Twice
This is a much more specific assertion, as it’s driven by the number of customers we’re configuring to be returned from the GetAllCustomers method from our customer repository fake. And if it’s one thing we’re after in unit testing, it’s to test all possible scenarios as specifically as possible.
We can improve this test by letting the data we use for configuration and behavior in the test setup drive the assertion. Here is the entire updated test class:
[TestFixture] public class WhenSendingEmailToTwoCustomers { private ISendEmail emailSender; private List<Customer> customers; [SetUp] public void Given() { emailSender = A.Fake<ISendEmail>(); customers = new List<Customer> { new Customer(), new Customer() }; var customerRepository = A.Fake<ICustomerRepository>(); A.CallTo(() => customerRepository.GetAllCustomers()).Returns(customers); var sut = new CustomerService(emailSender, customerRepository); sut.SendEmailToAllCustomers(); }
[Test] public void SendsTwoEmails() { A.CallTo(() => emailSender.SendMail()) .MustHaveHappened(Repeated.Exactly.Times(customers.Count)); } } |
Code Listing 76: Using Repeated.Exactly.Times() with customers.Count
We’ve changed two things here:
You should explore the other options that the Repeated class gives you when working with assertions with FakeItEasy.
Just as we can assert that a call to a fake happened, and happened a specific number of times, we can also assert that a call to a fake did not happen. In this case, we are testing the “non-happy” path in our example. We want to cover all scenarios when we write our unit tests.
In the previous section on MustHaveHappened, we tested the “happy” path of an email being sent to each customer returned from the CustomerRepository. Let’s test the path of an email not being sent because there are no customers returned from the CustomerRepository.
The implementation of the CustomerService class remains the same, but our test’s setup and assertion changes:
[TestFixture] public class WhenSendingEmailToAllCustomersAndNoCustomersExist { private ISendEmail emailSender; [SetUp] public void Given() { emailSender = A.Fake<ISendEmail>(); var customerRepository = A.Fake<ICustomerRepository>(); A.CallTo((() => customerRepository.GetAllCustomers())) .Returns(new List<Customer>()); var sut = new CustomerService(emailSender, customerRepository); sut.SendEmailToAllCustomers(); } [Test] public void DoesNotSendAnyEmail() { A.CallTo(() => emailSender.SendMail()).MustNotHaveHappened(); } } |
Code Listing 77: Using MustNotHaveHappened to test a call to the fake email sender did not happen
We’ve changed two things in our unit test:
Why did the call not happen? Let’s take a look at our CustomerService class again:
public class CustomerService { private readonly ISendEmail emailSender; private readonly ICustomerRepository customerRepository; public CustomerService(ISendEmail emailSender, ICustomerRepository customerRepository) { this.emailSender = emailSender; this.customerRepository = customerRepository; } public void SendEmailToAllCustomers() { var customers = customerRepository.GetAllCustomers(); foreach (var customer in customers) { emailSender.SendMail(); } } } |
Code Listing 78: The CustomerService class
Since we’ve configured our customer repository to return an empty list, there are no results to loop through. Because of this, the emailSender.SendMail code is never invoked.
In this chapter we’ve learned how to use assertions with FakeItEasy. We looked at examples of how to use MustHaveHappened and MustNotHaveHappened. From there, we looked at Repeated, and then refactored a unit test to allow the FakeItEasy test setup to power the test assertions. Now that we know how to use FakeItEasy assertions, let’s tackle how to use FakeItEasy with testing methods that take arguments.