CHAPTER 2
This chapter will walk us through trying to test a class that is untestable, refactoring to allow that class to be tested, creating a “stub” class for the sake of the test, and finally, using FakeItEasy instead of stub classes.
Let’s explore unit testing first without FakeItEasy. Let’s say we have to write a unit test for a class that currently has a dependency on a Repository class.
If you’re unfamiliar with the Repository pattern, you can read about it at https://lostechies.com/jimmybogard/2009/09/03/ddd-repository-implementation-patterns/ and at http://martinfowler.com/eaaCatalog/repository.html before continuing. At a very high level, the Repository pattern abstracts database queries and database code away from your classes that consume it.
The following class, CustomerService, has a dependency on a CustomerRepository class.
public class CustomerService { public Customer GetCustomerByCustomerId(int customerId) { var customerRepository = new CustomerRepository(); return customerRepository.GetCustomerBy(customerId); } } |
Code Listing 4: The CustomerService class with a dependency on the CustomerRepository class
You can see that we’re creating a new CustomerRepository in the GetCustomerByCustomerId method, and the repository uses the GetCustomerBy method to query the database for a Customer by a customerId.
Here’s what our unit test looks like:
[TestFixture] public class WhenGettingCustomerById { private const int customerId = 1; private Customer result; [SetUp] public void Given() { var sut = new CustomerService(); result = sut.GetCustomerByCustomerId(customerId); } [Test] public void ReturnsTheCorrectId() { Assert.That(result.Id, Is.EqualTo(customerId)); } } |
Code Listing 5: The unit test for CustomerService
When we go to run this unit test, we’re most likely going to get a failure. Why? Because the CustomerRepository class probably depends on runtime environment (for instance, access to a real database).
Note: I am not showing the implementation of the CustomerRepository class here because it’s not important. The way the CustomerRepository communicates with the database (ADO.NET, Entity Framework, NHibernate, RavenDb, etc.) does not matter. Just know that the class will be trying to reach out to some type of database to query for a “Customer” entity.
We know from the SOLID Principles (https://lostechies.com/chadmyers/2008/03/08/pablo-s-topic-of-the-month-march-solid-principles) that our CustomerService class should NOT be creating (aka, “newing up”) a CustomerRepository inline. We’re violating the Dependency Inversion Principle (https://lostechies.com/jimmybogard/2008/03/31/ptom-the-dependency-inversion-principle/).
We also know from good unit testing practices that we should never rely on infrastructure-related items like databases, web servers, and web services to be up and running for our unit tests to run.
Let’s fix this problem this problem by introducing Inversion of Control (http://stackoverflow.com/questions/3058/) and see how it changes our unit tests.
Here’s our new CustomerService class:
public class CustomerService { private readonly ICustomerRepository customerRepository; public CustomerService(ICustomerRepository customerRepository) { this.customerRepository = customerRepository; } public Customer GetCustomerByCustomerId(int customerId) { return customerRepository.GetCustomerBy(customerId); } } |
Code Listing 6: The CustomerService class now takes a dependency via its constructor
Note the following changes:
Note: For more information on how to use Microsoft Unity as a dependency injection container, please see Microsoft Unity Succinctly by Ricardo Peres.
Here is our ICustomerRepository interface:
public interface ICustomerRepository { Customer GetCustomerBy(int customerId); } |
Code Listing 7: The ICustomerRepository interface
We hit a problem when we go to refactor the unit test:
[TestFixture] public class WhenGettingCustomerById { private const int customerId = 1; private Customer result; [SetUp] public void Given() { var sut = new CustomerService(???); result = sut.GetCustomerByCustomerId(customerId); } [Test] public void ReturnsTheCorrectId() { Assert.That(result.Id, Is.EqualTo(customerId)); } } |
Code Listing 8: We need to pass an implementation to Customer Service’s constructor
You can see where I have inserted the “???” into the CustomerService’s constructor. I need to pass an implementation of ICustomerRepository, and since I can’t use the real CustomerRepository, I have to write a class that implements the interface.
I’ll have to write something called a stub.
Writing a stub class is simple enough; I need to write a new class that implements ICustomerRepository:
public class CustomerRepositoryStub : ICustomerRepository { public Customer GetCustomerBy(int customerId) { return new Customer { Id = customerId }; } } |
Code Listing 9: The stub class to impelment ICustomerRepository for our unit test
Now that the stub is written, we can use it in our unit test:
[TestFixture] public class WhenGettingCustomerById { private const int customerId = 1; private Customer result; [SetUp] public void Given() { var sut = new CustomerService(new CustomerRepositoryStub()); result = sut.GetCustomerByCustomerId(customerId); } [Test] public void ReturnsTheCorrectId() { Assert.That(result.Id, Is.EqualTo(customerId)); } } |
Code Listing 10: Providing a new CustomerRepositoryStub class to CustomerService’s constructor
Note how I create a new CustomerRepositoryStub class and pass it into the constructor of CustomerService.
Also note that the implementation of CustomerRepositoryStub returns a new Customer (like the real CustomerRepository would do) based on the Id I pass into the GetCustomerBy method.
When we run this test it passes.
We’ve taken an untestable class, made it testable, and refactored our unit test to correctly take an implementation of an interface and pass it to CustomerService’s constructor.
But we’re not done yet…
Let’s take a step back at this point and have another look at the CustomerRepositoryStub class. That’s one class I wrote for one very simple interface that was only used in one unit test.
What if I expect a different Id, FirstName, and LastName back on different tests for the same SUT? I have to write more stub classes.
What if I have to implement an interface with 20 methods on it, and those need to change based on the different setup of the SUT? My stub classes get larger.
What if I have to test multiple scenarios driven by conditional statements added to the CustomerService class dictated by the data returned on the Customer? I have to write more stub classes.
In my current project at work, we have almost 14,000 unit tests in our system. If I’m writing stub classes for most of, if not all of those tests, we’re talking about 14,000 extra classes at a minimum written just for the sake of unit testing.
Now change comes. An interface definition changes. Not only do you have to change the real class implementing interface (in our example, CustomerRepository), but ALL the stub classes you wrote for your unit tests!
There is a better way.
Let’s revisit the CustomerServiceTests class, but this time, instead of writing a stub class to implement an interface for the sake of the unit test, we’ll use FakeItEasy.
Here is the unit test using FakeItEasy:
[TestFixture] public class WhenGettingCustomerById { private const int customerId = 1; private Customer result; private Customer customer; [SetUp] public void Given() { customer = new Customer { Id = customerId };
//create our Fake var aFakeCustomerRepository = A.Fake<ICustomerRepository>();
//set expectations for the call to .GetCustomerBy A.CallTo(() => aFakeCustomerRepository.GetCustomerBy(customerId)) .Returns(customer);
var sut = new CustomerService(aFakeCustomerRepository); result = sut.GetCustomerByCustomerId(customerId); } [Test] public void ReturnsTheCorrectId() { Assert.That(result.Id, Is.EqualTo(customerId)); } } |
Code Listing 11: The unit test for CustomerService using FakeItEasy
There were a good number of changes made to the unit test, but the biggest changes are the introduction of FakeItEasy. Let’s take each line, one at a time.
var aFakeCustomerRepository = A.Fake<ICustomerRepository>(); |
Code Listing 12: Creating a fake
This is how you create a “fake” in FakeItEasy. Unlike other mocking frameworks, where you have to differentiate between creating “stubs,” “spies,” or “mocks,” FakeItEasy treats everything as a fake. In this case, creating our fake is very straightforward; we ask FakeItEasy to create a fake of ICustomerRepository for us.
Now that we have our fake created, we configure calls on the fake that allow us to test a certain scenario in our SUT. For this simple example, there are no conditional statements in our SUT; we’re just asserting that we expect a customer with a matching ID to be returned for the ID we’re passing into the GetCustomerBy method:
A.CallTo(() => aFakeCustomerRepository.GetCustomerBy(customerId)).Returns(customer); |
Code Listing 13: Configuring a call to the fake and specifying a return value
A.CallTo allows us to configure the fake so we can start telling it what to do. We use the fake to call the GetCustomerBy method passing it the customerId that we set up in our unit test, and return a specific customer (which also was set up in our unit test). A.CallTo allows us to set up our expectations of what should happen in this unit test when this call is made.
If we were to try to pass a different customerId to the GetCustomerBy method, when we went to assert matching customer IDs in the test method, our unit test would fail.
Finally, we specify the fake’s behavior by using Returns. Using Returns allows us to control what our configured fake will return for the given configuration. In this case, we’re specifying that the customer set up in our unit test should be returned by the configured call. If we were to return a different customer in the Returns portion of the FakeItEasy call here, our unit test would fail.
As you can see, FakeItEasy allows us to not only assert that the call to GetCustomerBy takes a specific customerId, but it also allows us to assert that a specific customer is returned.
And finally, we pass the fake to the CustomerService’s constructor:
var sut = new CustomerService(aFakeCustomerRepository); |
Code Listing 14: Passing the faked CustomerRepository to CustomerService
The constructor takes the fake, because it sees aFakeCustomerRepository as an implementation of ICustomerRepository.
The rest of the unit test flows as before, and our test passes.
By adding a couple lines to our unit test, we’ve introduced FakeItEasy, set expectations on calls made to the fake’s method, arguments, and return value, and eliminated the need to write a stub class.
In this chapter, we started with untestable code, refactored to using stubs after introducing dependency injection to allow our class to be unit tested, and ended with the final refactoring to FakeItEasy, which removed the need to write a unit testing stub class. Next, we’ll take a look at the rules that govern how FakeItEasy can be used.