Unit Testing - a journey in Mock Frameworks

Sign in to queue

Description

Unit testing is a way to test your code to see if it's behaving as you expected. Ussualy you do that on a method.

In the process of testing you want to isolate some parts of a functionality and to focus only on the current behavior that you want to test. Let's say that you want to test if an entity it's validated when you insert it in the database, you want to isolate the part where you call the database, because you just want to test if it's validated. You can do this in 2 ways, with mocks and with stubs.

Mocks Aren't Stubs, but what is the difference?

Stub a simple, fake implementation that conforms to the interface and is to be used for testing (can't fail your tests).

Mock is a stub with an assertion that the method gets called (can fail your tests).

Mocks have the same lifecycle as stubs, the only difference is that mock sets up and verifies some expectations.

  1. Setup – Create mocks and/or stubs for test
  2. Setup expectations – Tells the mock object what you expect to happen (like calling a method).
  3. Exercise - Test the behavior
  4. Verify expectations – Verify if the mock expectations have been met
  5. Verify state - Verify state of objects
  6. Cleans up resources

Luckily there are some frameworks that do this for us. The most usual mock frameworks are Moq, FakeItEasy and RhinoMocks plus others.

Moq can be installed via nuget an it's so simple that you don't need to read documentation to use it. Some of the features that Moq has can be found below.

  • Strongly-typed
  • Visual Studio intellisense integration
  • No Record/Replay syntax
  • AAA (Arrange Act Assert)
  • Mock both interfaces and classes
  • Override expectations

FakeItEasy is very similar with Moq, and can be downloaded via nuget.

"I created FakeItEasy so [...] there are no major differences between Moq and FakeItEasy." - Patrik Hägne

RhinoMocks (from the creator of NHibernate) has almost the same advantages as Moq, although you can still find some of the Record/Playback syntax, it was added version 3.5+ which introduced  AAA.

Note: For the next examples I'm using NUnit to run my tests, other frameworks will work as well. I will take in consideration only Moq and RhinoMocks. The entire project can be downloaded from github.

Basic Example

Let's see a basic example where we try to see if method Save from PersonRepository was called inside PersonService with Moq and RhinoMocks. We will mock the PersonRepository so the database can't be accesed. This means that we isolate the database access from test.

Moq

In the example below we will notice what it take to create a mock with Moq. For that we need just to write

new Mock<>()

 After the mock is created we use mock.Object to pass it in the constructor. We can see that mockPersonRepository is the mock and personValidator is the stub, although the difference is only conceptual.

We tell the personValidator to return true for any person that is validated using It.IsAny<Person>(). The test will pass because personValidator returns true, if we set it up to return false, the test will fail. I know what you think personValidator failed our test, but that's not true, the test failed because the expectations were not accomplished as we can see from the message below.

Generic Episode Image

The method Save from  mockPersonRepository was never called and the Verify(m => m.Save(person), Times.Once); failed our test.

In method Verify we set up the expectation m => m.Save(person), Times.Once and we check the expectations.

[Test]
public void RegisterNewPerson_SaveThePerson_WhenThePersonIsValid()
{
    //Arrange
    var mockPersonRepository = new Mock<IPersonRepository>(); 
    var personValidator = new Mock<IValidator<Person>>();
    personValidator.Setup(x => x.Validate(It.IsAny<Person>())).Returns(true);
    var personService = new PersonService(mockPersonRepository.Object, personValidator.Object);

    var person = new Person
    {
        Id = 123,
        FirstName = "Adrian",
        LastName = "Cadar"
    };

    //Act
    personService.Register(person);

    //Assert
    mockPersonRepository.Verify(m => m.Save(person), Times.Once);
}

RhinoMocks

Here we can specify directly who is the mock using MockRepository.GenerateMock and who is the stub using MockRepository.GenerateStub.

The same story goes here with the validator, if we want to return true for any person that enters we use this syntax Arg<Person>.Is.Anything.

Here we can set up and verify expectations in 2 ways.

First way will be to call method Expect(x => x.Save(person)) and tell it that we want this to happen only one time with Repeat.Once();. This is the expectation setup and below //Assert we call VerifyAllExpectations();. This is more close to how we describe de lifecycle of a mock, although I prefer the second method.

The second way will be very similar to Moq  mockPersonRepository.AssertWasCalled(x => x.Save(person), x=>x.Repeat.Once()); and instead of Verify we will use AssertWasCalled.

public void RegisterNewPerson_SaveThePerson_WhenThePersonIsValid()
{
    //Arrange
    var person = new Person
    {
        Id = 123,
        FirstName = "Adrian",
        LastName = "Cadar"
    };

    var mockPersonRepository = MockRepository.GenerateMock<IPersonRepository>();
    var stubPersonValidator = MockRepository.GenerateStub<IValidator<Person>>();
    //mockPersonRepository.Expect(x => x.Save(person)).Repeat.Once();

    stubPersonValidator.Stub(x => x.Validate(Arg<Person>.Is.Anything)).Return(true);
    var personService = new PersonService(mockPersonRepository, stubPersonValidator);

    //Act
    personService.Register(person);

    //Assert
    mockPersonRepository.AssertWasCalled(x => x.Save(person), x=>x.Repeat.Once());
    //mockPersonRepository.VerifyAllExpectations();
}

 

Argument Constraints

We will use argument constraints to match a certain criteria and we will notice some differences between Moq and RhinoMocks. In the examples below we are trying to isolate access to database at deleting, saving and query. The role of argument constraints are to make sure that this mocked methods are receiving at we are expecting.

Moq

In the example below we are checking if the person that enters the method Save and matches certain conditions. This will make us sure that the data from entity didn't suffer any changes in Service.

mockPersonRepository.Verify(x => x.Save(It.Is<Person>(p => p.Id == id && p.FirstName == firstName && p.LastName == lastName)));

If we use DeletePersons, we can make sure that the list we pass into Service has the same size or contains a certain person.

mockPersonRepository.Verify(m => m.DeletePersons(It.Is<List<Person>>(l => l.Count == 3))); 
mockPersonRepository.Verify(m => m.DeletePersons(It.Is<List<Person>>(l => l.Contains(person1))));

Other examples of checking arguments will look like following:

mockPersonRepository.Verify(x => x.GetPersonByFirstName(It.Is<string>(s => s.Contains("one"))));
mockPersonRepository.Verify(x => x.GetPersonByFirstName(It.Is<string>(s => s.EndsWith("nes")))); 
mockPersonRepository.Verify(x => x.GetPersonByFirstName(It.Is<string>(s => s.StartsWith("Jon"))));

RhinoMocks

Checking if the person that enters the method Save matches a certain criteria is done the same way as Moq.

ParamsRegisterNewPerson_SaveThePerson_WhenThePersonIsValid

mockPersonRepository.AssertWasCalled(x => x.Save(Arg<Person>.Matches(p => p.Id == id && p.FirstName == firstName && p.LastName == lastName)));

At DeletePersons we can see that RhinoMocks comes with some functionalities built in like ContainsAll that make sure all persons are in the list and Equal which make sure that the list that enters DeletePersons is equal to the list given, meaning the objects are compared by reference and if one or many are objects missing or are in addition, the test will fail.

mockPersonRepository.AssertWasCalled(m => m.DeletePersons(Arg<List<Person>>.List.ContainsAll(new List<Person> { person1, person2, person3 })));
mockPersonRepository.AssertWasCalled(m => m.DeletePersons(Arg<List<Person>>.List.Equal(new List<Person> { person1, person2, person3 })));
mockPersonRepository.AssertWasCalled(m => m.DeletePersons(Arg<List<Person>>.List.Count(Rhino.Mocks.Constraints.Is.Equal(3))));
mockPersonRepository.AssertWasCalled(m => m.DeletePersons(Arg<List<Person>>.List.IsIn(person1)));

 

Testing the text that enters the method is done the same way as Moq, but in addition to that the RhinoMocks has the Like functionality that will match a text with wildcards.

mockPersonRepository.AssertWasCalled(x => x.GetPersonByFirstName(Arg<string>.Matches(Rhino.Mocks.Constraints.Text.Contains("one"))));
mockPersonRepository.AssertWasCalled(x => x.GetPersonByFirstName(Arg<string>.Matches(Rhino.Mocks.Constraints.Text.EndsWith("nes"))));
mockPersonRepository.AssertWasCalled(x => x.GetPersonByFirstName(Arg<string>.Matches(Rhino.Mocks.Constraints.Text.StartsWith("Jon"))));
mockPersonRepository.AssertWasCalled(x => x.GetPersonByFirstName(Arg<string>.Matches(Rhino.Mocks.Constraints.Text.Like("Jon.*"))));

 If some functionalities of Moq are not present, these can be implemented to match your needs.

Out/Ref Parameters

Sometimes you have to isolate (ignore) the out/ref parameters from a method.

Moq

In the example below, the out parameter are practically ignored and the test will continue.

personValidator.Setup(x => x.Validate(It.IsAny<Person>(), out person)).Returns(true);

In the example with ref parameter below, if the parameters are instantiated inside the given class, the test will fail because the given argument in stub and the one that is given to personService don't have the same reference, like in the message below.

Generic Episode Image

How can you solve this? You can find a solution on stackoverflow.

// Only matches if the ref argument to the invocation is the same instance
personValidator.Setup(x => x.Validate(It.IsAny<Person>(), ref person)).Returns(true);

 

RhinoMocks

Here you can bypass out parameter by setting up the argument to be dummy. If you don't do this and just put out person like in Moq, the test will fail and you will be required to specify the arguments like in the message below.

Generic Episode Image

// Out
personValidator.Stub(x => x.ValidateOut(Arg<Person>.Is.Anything, out Arg<Person>.Out(person).Dummy)).Return(true);

Luckily the ref parameter can be very easy isolated and the test will continue.

Generic Episode Image

// Ref

personValidator.Stub(x => x.ValidateRef(Arg<Person>.Is.Anything, ref Arg<Person>.Ref(Rhino.Mocks.Constraints.Is.Anything(), new Person()).Dummy)).Return(true);

This is just a part of features that this frameworks has, you can find more in the documentation of each framework.

The reason why you mock your code is because you want to accomplish your mission, to test your code.                                                                                                                                                                                                                    Adrian Cadar

Tags:

C#, Unit Testing

The Discussion

Add Your 2 Cents