.Net Unit Testing with Moq, Autofixture and Fluent Assertions - Part 2

Dave Toland

24/04/2020

This is Part 2, click here for Part 1.

Unit Testing is an art form. As developers we probably all know the importance of writing unit tests, even if that's only in theory. Many of us would like to say we regularly do it, but the reality is that it's time consuming, takes practice, and can involve writing a ton of unnecessary code if we don't have the right tools at our disposal.

Moq, Autofixture and Fluent Assertions

Moq is the self-titled 'most popular mocking framework for .Net' and whether it actually is or not, it does its job very well. It's not the only mocking framework, there are several others, but you can investigate those on your own time. With me, when I select a tool like this, I'll often go with one that's got a broad user community and is well documented. At this point it's worth pointing out that Moq itself is woefully under gunned in the self-documentation field compared to other tools and packages, but its saving grace is the volume of blog posts and stack overflow content on it. It's safe to say Moq is very widely used, and that means you'll generally be able to find information about what you're trying to do or are stuck on.

AutoFixture is a tool for generating dummy data, and if you're writing unit tests, you'll no doubt be painfully aware of how much dummy data you need to generate just to get things into a state where you can actually execute a meaningful test. There is a reasonable amount of documentation on their site with some decent examples to get you started, but once you've got the general idea of how it works there's not a great deal more you need to know about it.

Fluent Assertions is a set of extension methods that replace the standard Assert.*** syntax you're probably used to using. To be fair, Moq and Autofixture are the main players here, the Fluent Assertions library is not a requirement as much as a nice to have. I've included it in this post though because firstly I think it's absolutely brilliant, but moreover because it just makes tests that much easier to read and write. As its name suggests, it's fluent, the syntax just makes tests more readable. Its documentation is also second to none, in fact I would go as far to say the guys over there have produced some of the best, most comprehensive documentation I've ever seen. Tons of examples, covering virtually every possible way you'd want to use it.

Let's dive straight in with an example of all three in action..

  [TestFixture]
  public class UserServiceTests
  {
      private Fixture _fixture;
      private Mock<IUserRepository> _mockRepo;
      private UserService _service;


      [SetUp]
      public void Setup()
      {
          _fixture = new Fixture();
          _mockRepo = new Mock<IUserRepository>()
          _service = new UserService(_mockRepo.Object);
      }
    
      [Test]
      public void AddUser_NewUserWithDuplicateId_ThrowsException()
      {
          _mockRepo.Setup(x => x.AddUser(It.IsAny())).Returns(false);
          var newUser = _fixture.Create<User>();
    
          _service.Invoking(x => x.AddUser(newUser))
              .Should().Throw<RepositoryException>()
              .WithMessage($"User with Id {newUser.Id} already exists");
      }

  }

So what's going on here?

  • [4-6] : Sets up an instance of AutoFixture, a Mock repo and a UserService
  • [11-12] : Instantiates the instance of AutoFixture, and our mock repo
  • [13] : Instantiates the service - our system under test (or SUT) with the mock repo
  • [19] : Configures our mock repo to return false regardless of what user Id is given to it
  • [20] : Creates a new User using AutoFixture to populate all the properties of it
  • [22] : Uses Fluent Assertions to assert that this call will not only throw an exception of a particular type but also that it should have a particular message within it, containing a property (Id) of the user we've created.

What are the key points to take from this?

For context, when the AddUser method of the service is called, it tries to add a new User into the repo. If the repo returns false, it means a user with that Id already exists, and our service should then throw an exception. This is somewhat simplified down for this demo, but that's what happening under the covers.

Firstly, we're using Moq to create a repository.

_mockRepo = new Mock<IUserRepository>()

Remember, the UserService is our system under test here, we actually don't care at this point what the Repository is or what it does, as long as it returns false when we try to add a new User.

_mockRepo.Setup(x => x.AddUser(It.IsAny())).Returns(false);

By using the Setup method, we're telling our mock what to do when the AddUser method is called. We're using the It.IsAny<int> call to tell Moq that any int passed in here is fine, and then we're using the Returns method to define what gets returned from this call. Essentially, we're saying "when the AddUser method is called with any input int, return false". The Mock is actually a special object, and so when we want to use the actual mocked repo, as per the new UserService method param, we need to use the .Object property to access the underlying mocked object.

Once we've set all this up, we then want to call our service and try to add a user with an Id that already exists. If you had to code all of this manually, you'd have to do something like:

  • Create a mocked version of the IUserRepository, something like MockUserRepository : IUserRepository
  • Implement the AddUser method (and every other method it defines, which presumably it does, stubbing them all out so they compile)
  • Write some logic that determines what to do when a duplicate user Id is passed in (in our case, throw an exception)
  • Perhaps write further logic to determine behaviour when a user with a 'good' Id is passed in, if not now, as part of further tests
  • Instantiate an instance of the MockUserRepository
  • Instantiate an instance of our SUT, passing in the mock repo instance

..and that's just the setup, we haven't even written the test yet.

Moving on to the actual test. On line 20 we create an instance of our User.

Again, at this point we don't care what the user object looks like, it obviously has an Id property, it could have 20 other properties, or 100. If those properties were all nullable, we could get away with just creating a new User, but invariably our classes contain complex properties and variables that need actual values. At the very least, we'd need to set the Id property and match that to the logic we've written in the AddUser method of our MockUserRepository. This is where AutoFixture comes in...

var newUser = _fixture.Create<User>();

We could set the Id manually, to something like 1, or 99, or whatever, and it's 'whatever' that's the point here. Do we actually care what the Id is? No. We've already told our mock that any int we pass in should return false. So, we tell AutoFixture to just create a User. It knows that the Id property is an int and so gives it any int value. If you really want to, you can set a breakpoint on line 22 and inspect it, it'll be some random number, but who cares.

Without AutoFixture we'd have to create some sort of factory class or method to generate a User that we don't care about (presumably several different versions for different tests), or worse - do all that inline in the test. Maybe we'd then create a const with some arbitrary number and then set this into the Id property and then later assert against it. AutoFixture removes all that boiler plate code, saves the time it would take to write it, and gives you access to the property we're going to assert against in just one line.

Finally, the assertion.

_service.Invoking(x => x.AddUser(newUser))
  .Should().Throw<RepositoryException>()
  .WithMessage($"User with Id {newUser.Id} already exists");

I'll admit, the first part isn't the most readable... Invoking? what's that? Basically, it's an inline way of writing an Action that we can assert throws an exception. I could have used a more basic example that doesn't assert against a throw, but this is about as complex as it gets with Fluent Assertions and we're on a roll now, right? Ok, I'll rewrite this in a more readable, albeit slightly more verbose form:

var action = () => _service.AddUser(newUser);
action.Should().Throw<RepositoryException>()
  .WithMessage($"User with Id {newUser.Id} already exists");

This is the power of Fluent Assertions, it just reads like you'd say it:

AddUser should throw a RepositoryException with the message: "User with Id X already exists"

I always used to read standard assertions and think of Yoda, for example

Assert.IsFalse(isReadable)

It's all backwards.. assert is false is readable. Yeah, false alright. I mean, even the word Assert isn't the most readable word when you think about it, sure we know what it means, but it's not instantly obvious like "should", especially when you're talking to people that don't write unit tests. Anyone that's written a lot of unit tests will probably be quite used to it, but have you ever heard a technical writer describe anything like that? Personally, I hear them say things like "Given invalid input data, the system should display an error", or "Then the result of the calculation should be the total of inputs" that's why I love the syntax of Fluent Assertions. It allows you to write tests that are grammatically closer to the requirements that have been written (unless your requirements were actually written by Yoda).

Hopefully this has given you a taste of these three tools, they can not only save you time and increase your own productivity, but they also improve the readability of your tests which increases the productivity of anyone else that tries to understand them. Above all else, they dramatically reduce the amount of complex and unnecessary code you have to write to accomplish what should be a relatively straight forward task.

In Part 3 I'll delve into some more of the nitty gritty of writing unit tests with the power of Moq, Autofixture and Fluent Assertions. We'll start exploring more of the setup logic, cover more complex return scenarios, callbacks, and building powerful factory methods around Autofixture.