Unit Testing
When undertaking unit tests it’s essential that the tests be repeatable. This means that developers need to ensure that the database is in an expected state at the beginning of each test run. To achieve this, tests can either be run against a real database with transactions or run against fake objects in memory. This guide explores both approaches. Sample code uses the NUnit unit testing product, but can easily be adapted to any unit testing framework.
Using a Real Database
Setup a test version of your database. Typically we have set up two databases for a project:
· ApplicationDatabase
· ApplicationDatabase_Test
All of the test data will be stored in the ApplicationDatabase_Test.
Next, create a transaction test fixture. The benefit of this is that all our test fixture classes can derive from the custom test fixture class and be ready with the transactional pattern for testing in place.
An example of the transactional TestFixtureBase |
[TestFixture] |
In this code all tests are run within a TransactionScope and the changes are rolled back upon completion of the test.
Additionally, note that in the SetUp method we are creating a new unit of work and in the TearDown method we are destroying it by calling IUnitOfWork.Dispose. The creation of new units of work for each test is important because we want complete test isolation; that is, no test can affect the result of any other test. We achieve this isolation by having each test run in a new unit of work.
Once this is done the development of the actual tests can commence.
A simple unit test |
[Test] |
This test finds the Contribution with Id 1 and then updates its Description attribute. We then call SaveChanges to flush our pending update to the database. Notice we pass true to the SaveChanges method. This is an idiomatic LightSpeed unit test pattern applicable whenever we want to verify a database update was made correctly. The SaveChanges method causes all pending updates to be flushed to the database, and the true argument signals that LightSpeed should additionally clear the identity map – the unit of work’s cache of the entities it is managing, which serves to prevent us loading the same Entity more than once. By clearing the identity map, we guarantee that subsequent requests for previously loaded objects hit the database and therefore allow us to validate that the database state of an entity is as we expect.
Using Fakes
In situations where in memory objects would be preferred there are helper classes included in LightSpeed to create fake entities and a test unit of work.
LightSpeed includes two classes to help support faking queries and entities: TestUnitOfWork and EntityFactory. Both of these are in the Mindscape.LightSpeed.Testing namespace.
TestUnitOfWork allows you to inject a fake result for a query. To do this, call the TestUnitOfWork.SetCollectionResult method, passing in the desired fake result. Subsequent calls to Find or FindBySql, or LINQ entity queries, will return the fake result rather than querying the database. TestUnitOfWork also allows faking of other query methods:
· Call TestUnitOfWork.SetSingleResult to specify a fake result for FindOne or FindById
· Call TestUnitOfWork.SetExpectedCountResult to specify a fake result for Count, or the LINQ Count() and LongCount() operators
· Call TestUnitOfWork.SetExpectedCalculateResult to specify a fake result for Calculate, or for LINQ aggregate operators such as Sum()
· Call TestUnitOfWork.SetProjectionResult to specify a fake result for Project, or for LINQ projection queries (where specific properties are selected into a named or anonymous type)
· Call TestUnitOfWork.SetSearchResult to specify a fake result for Search
Note that fake results are returned directly – any query expression or LINQ Where operator is not applied! Your test fixture should set up the expected fake results.
Creating a TestUnitOfWork and adding then selecting a fake entity |
[Test] |
EntityFactory helps you to create entities with predictable ID values and in states other than the New state. Thus, you can use EntityFactory to fake entities that have been loaded from the database (and therefore begin in the Default state).
Using Mocks
If you are interested in interaction based unit testing (often characterized by the use of mocks, fakes or stubs), LightSpeed exposes a few key interfaces that facilitate such an approach. The primary extension point providing substitutability in LightSpeed is the IUnitOfWork interface. Implementing (or mocking) IUnitOfWork provides complete control over all major LightSpeed operations such as querying and persistence. In order to make your test context use your mock IUnitOfWork, create an implementation of IUnitOfWorkFactory that returns your custom implementation and then assign your custom IUnitOfWorkFactory to the UnitOfWorkFactory property found on the LightSpeedContext object.
Some places in LightSpeed internally require a UnitOfWorkBase, not just an IUnitOfWork. Consider mocking UnitOfWorkBase rather than the interface, or using the TestUnitOfWork fake instead.