Thursday, November 6, 2014

Action Verification Patterns

I was reminded again about a verification pattern that I use frequently when doing TDD/unit tests. It's really two different patterns. The first when you want to ensure that some action occurs, the other when you want to make sure it hasn't.

My examples will be specific to FakeItEasy, but you should be able to extrapolate them to any mocking framework.

The use case for my specific example is the case when you're creating a new entity via WebAPI and you may be connecting that entity to an existing related entity or creating a new entity for the other end of the relationship as well. For example, you want to sign up a new user but may be associating that new user with an existing company or, if there is no existing company, create a new one at the same time as you create the user. In this case, among other tests, you'll want to test that the new company either was or was not created as appropriate.

The patterns come into play in the Assert step of the test. With the mocking framework you ensure that the call across the system boundary, in this case to the database, either did or did not happen. When testing that call was made, use the verification signature that specifies exactly which entity was to have been added.

For example,

A.CallTo(() => _testContext.MockCompanies
                           .Add(A<Company>.That
                                          .Matches(c => c.Name == expectedCompanyName))
 .MustHaveHappened();


The other pattern, when you want to exclude an action, is similar except you use the verification signature that accepts any matching entity.

A.CallTo(() => _testContext.MockCompanies.Add(A<Company>.Ignored))
 .MustNotHaveHappened();


In the first, case you make sure that the exact expected instance has really been added to the database. In the second, you ensure that no entity of that type has been added to the database. By matching on all entities in the second case you avoid an over-specification in which an error in the spec might give you a false positive by failing to match a call because of an error in the condition.