Unit Testing Non-Deterministic Values

Sometimes we will need to set random or non-deterministic values in our code. Each time we execute across those values, whether in production or a unit test, the newly generated value will differ from the ones produced on previous runs. Randomised numbers, strings or GUIDs (a.k.a. UUIDs), the current system time, and many other effects fall into this category of non-deterministic scalar values.  

Registering a new customer 

In this C# example, we want to register a new customer into our system. We have already defined the high-level business logic inside a RegisterCustomerUseCase class: 

 

  public class RegisterCustomerUseCase : IRegisterCustomerUseCase
  {
     public ICustomerRepository Repository { get; }

     public RegisterCustomerUseCase(ICustomerRepository repository)
     {
        Repository = repository;
     }

     public async Task<Customer> Register(CustomerRegistration reg)
     {
        await Validate(reg);
        var customer = reg.ToCustomer();
        await Repository.SaveCustomer(customer);
        return customer;
     }

     private async Task Validate(CustomerRegistration reg)
     {
        if (reg == null)
           throw new MissingCustomerRegistration();
        reg.Validate();
        var existCust = await Repository.GetCustomer(reg.EmailAddress);
        if (existCust != null)
           throw new DuplicateCustomerEmailAddress(reg.EmailAddress);
     }
  }

 

The public Register() method is the behaviour we want to verify with unit tests. 

In particular, we are interested in unit testing the non-deterministic value-setting residing in the call to ToCustomer(). Here, our incoming CustomerRegistration instance produces and returns the to-be-registered Customer object, including a unique GUID customer identifier:

  public Customer ToCustomer()
  {
     return new Customer(Guid.NewGuid(), FirstName, LastName, EmailAddress);
  }

As an aside, the CustomerRegistration and Customer classes each model distinct concerns: CustomerRegistration contains and validates input data, while the Customer class represents a valid customer, including a customer identifier. 

 

Each time we run Guid.NewGuid(), a new, unique GUID value is generated. I ran Guid.NewGuid() three times on my laptop and got these values:

   {ee491102-4cc2-4fdf-aca8-fdfc1239cc83}
   {9a182eff-dd9d-4ca8-8dd9-34630581be74}
   {9608dc83-97b6-4453-b395-e3e3a7f4a4e9}

So, how do we assure that ToCustomer() converts the registration data into a customer instance, including a valid and unique GUID for the customer.Id?

 

To unit test the newly generated Customer instance, we must have access to it outside the Register() method. Yet, local variable customer will fall out of scope when Register() returns. Lucky for us, Register() returns customer! Callers of Register() will have access to the new customer instance because they receive it in the return value! 

Now that we have access to the new customer, we can unit test (using the XUnit & Moq frameworks) as follows: 

     [Theory]
     [InlineData("Fred", "Flintstone", "fred@flintstones.net")]
     [InlineData("Barney", "Rubble", "barney@rubbles.rock")]
     [InlineData("Wilma", "Flintstone", "wilma@flintstones.net")]
     public async Task If_New_Customer_When_Call_Register_Then_Create_Customer_From_Registration(string firstName,
        string lastName, string emailAddress)
     {
        var mockCustomerRepo = new Mock<ICustomerRepository>();
        var reg = new CustomerRegistration(firstName, lastName, emailAddress);
        var useCase = new RegisterCustomerUseCase(mockCustomerRepo.Object);
        var customer = await useCase.Register(reg);
        VerifyNewCustomer(customer, reg);
     }

     private static void VerifyNewCustomer(Customer customer, CustomerRegistration reg)
     {
        customer.Id.Should().NotBeEmpty();
        customer.FirstName.Should().Be(reg.FirstName);
        customer.LastName.Should().Be(reg.LastName);
        customer.EmailAddress.Should().Be(reg.EmailAddress);
     }

The unit test and its helper method VerifyNewCustomer() check for the correct state inside the customer object. 

Or do they? 

 

FirstName, LastName and EmailAddress are as per the incoming registration data and correctly assigned in ToCustomer()—No problem there.

 

But the assertion check on customer.Id is minimal:

     customer.Id.Should().NotBeEmpty();

We’re only verifying that the Id value is not the empty GUID, i.e. {00000000-0000-0000-0000-000000000000}. That’s it. 

 

To show that this is suboptimal, check out an alternative version of ToCustomer():

     public Customer ToCustomer()
     {
        return new Customer(new Guid("G9D7AB3E-1697-4886-B967-0F3E38F468E4"), FirstName, LastName, EmailAddress);
     }

And still, the unit test passes, even with a hard-coded customer.Id! Yet the requirements call for unique Id values. 

 

What are we to do? How do we ensure those unique values?

 

Take 1-2 minutes and look at the definition of Register() and try to find the solution to our puzzle: How can we detect, via a unit test, the uniqueness (furthermore, the correctness) of the customer identifier? 

Again, here’s the Register() method:

  public async Task<Customer> Register(CustomerRegistration reg)
  {
     await Validate(reg);
     var customer = reg.ToCustomer();
     await Repository.SaveCustomer(customer);
     return customer;
  }

Note: Scientific research confirms that you’ll learn more if you try to solve a problem by yourself first—even if you are unsuccessful in your attempt.

Solution

You may have noticed that Register() saves the new customer to the Repository. Whatever customer values we save should be the same as the customer returned by Register()

Therefore the following unit test will sort us out:

     [Theory]
     [InlineData("Fred", "Flintstone", "fred@flintstones.net")]
     [InlineData("Barney", "Rubble", "barney@rubbles.rock")]
     [InlineData("Wilma", "Flintstone", "wilma@flintstones.net")]
     public async Task Given_New_Customer_When_Call_Register_Then_Save_Customer_To_Repository(
        string firstName, string lastName, string emailAddress)
     {
        var mockCustomerRepo = new Mock<ICustomerRepository>();
        var registration = new CustomerRegistration(firstName, lastName, emailAddress);
        var useCase = new RegisterCustomerUseCase(mockCustomerRepo.Object);
        var customer = await useCase.Register(registration);
        mockCustomerRepo.Verify(x => x.SaveCustomer(customer));
     }

The last two lines are where all the important stuff happens:

        var customer = await useCase.Register(registration);

Here we have the code exercising the Register() method and taking in the given registration data. Critically, we are capturing the return value of the customer, including the unique customer.Id.

 

The next line is equally important:

        mockCustomerRepo.Verify(x => x.SaveCustomer(customer));

It provides us with another handle on the newly produced customer: We’re verifying that the Repository‘s SaveCustomer() method is being called with a customer reference containing exactly this FirstName, LastName, EmailAddress and most importantly, the Id! We got a handle on the unique customer identifier!

Conclusion

Non-deterministic values can make for tricky unit testing. When we have only one copy of the non-deterministic value, we don’t have a control value to compare it to. The solution is to have two copies of the non-deterministic value that have travelled down different paths: For example, one is saved (to an abstraction of) a data store while the other becomes a return value.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply