top of page
  • Writer's pictureScott J. Swindell

Repository & Unit of Work Design Patterns

One component, upon which at many enterprise applications rely, is some form of database. It's often hard to get by without one or more, whether it's SQL, No SQL, or yet another technology. However, when you're unit testing such an application, you don't want to actually test against these real databases.

For starters, this gives you a huge performance hit. For a few unit tests, this might not be noticeable. However, once you start getting hundreds of unit tests in a gated build, this can really be problematic.

Second, testing against a real database means that your unit tests are dependent on the state of the database and each other.

One unit test can inadvertently affect another unit test, by altering data that is used by both. So tests might work one moment and fail the next. This can be extremely difficult to debug.

If that still hasn't convinced you, think of the potential deadlocks when all the unit tests being run by your builds and developers are all vying for attention from the test database. As you can imagine, this can be another performance hit and another opportunity to introduce intermittent failures.

Ultimately, when you test against an actual database, you're testing your Object-Relational Mapping (ORM) just as much as your own code. You want your unit tests to focus on testing your code, and leave the ORM and database for integration testing.

So, how do you provide access to your data, while still enabling unit testing? Let me introduce you to the Repository and Unit of Work design patterns. The Unit of Work encapsulates the database context, while the Repositories provide access to your data. All the while, everything is defined by interfaces so it can be nicely mocked by your chosen mocking framework. Take a look at the Unit of Work interface and implementation.

The Unit of Work is the sole provider of access to the Repositories. When it creates a Repository, it initializes it with the shared database context, which is encapsulated in the Unit of Work itself. Therefore, when any Repository performs an action, it's using the same context as the other Repositories. Furthermore, nothing is allowed access to the context except the Repositories.

In order to actually create a Unit of Work, a Factory is used to hide the underlying implementation. That means you can create a Unit of Work for unit testing as well as normal operations.

Once you have your Unit of Work, you can use it to access the Repositories. Take a look at the interface and implementation of the Repository.

The Repository class itself provides a generic implementation to perform CRUD operations on any type of data entity. Now that we have the Unit of Work and Repository implemented, it's time to take a look at a real example of how to use them. This example shows what a possible Service class might look like that consumes these interfaces.

As you can see from the example, the service used two Repositories to accomplish it's task. When it was done, it called SaveChanges, which committed the add as a single transaction.

Bringing it all together, we have the Unit of Work, which is responsible for the database context and the creation of the Repositories. Then the Repository is a generic interface which provides operations on any data entity. Finally, the Unit of Work provides a Save method to commit the operations as a single transaction.

Since everything implements an interface, it can all be mocked. Therefore, you have data access that can be easily unit tested without the need for a cumbersome test database.

 

The complete source code from this article can be found here.

bottom of page