While I was working on an article for a magazine I came to the realization that building a testable Silverlight RIA application that uses the new .NET RIA services framework can be quite hard. There’s a lot that is organized by the framework. Because of that it can be quite an undertaking to stub all the framework components so you can focus your tests on the bits that you’ve programmed yourself.
In this article I’m going to show how you can build domain services for a .NET RIA services application in such a way that you can unit-test them.
Domain service structure for unit-testing
The default infrastructure for domain services generated by the .NET RIA services framework sadly isn’t all that great. There is simply no way to replace some parts with stubs so you can focus on testing a single piece of functionality inside the domain service. Before you can really start unit-testing the functionality of the domain service you need to tear it apart. By tearing apart I mean replacing the generated components for some manually coded components.
The domain service structure I’m after in this article looks similar to the structure presented in the figure below.
Once the domain service is completed you will end up with a façade that implements all the requirements for the domain service contract required by the .NET RIA Services framework. Internally this domain service will use repository classes to communicate with the database. A domain service usually governs a single island of data within your application. An island of data can be an order with order items or customers with addresses and contacts. With design proposed in the first figure you will end up having one domain service that uses several repository classes.
The repository class is responsible for talking to the database. Although you can use any technology that supports LINQ, I’m going to stick to LINQ to SQL for this article. Before I’m going to explain how I made the repository classes for the sample I am first going to talk a little about the requirements of the repository classes used in combination with a domain service.
Domain services expose query methods that return an IQueryable<T> interface instead of a set of data. This enables ADO.NET entity framework and the LINQ to SQL framework to build intelligent database queries based on filters, ordering and other query manipulation settings provided by the client. A repository should support this kind of functionality. At the very least it should return resultsets as an IQueryable<T>. More on that later in the article.
When performing data-manipulation the domain service does not immediately commit data when the Insert, Update or Delete methods are called on the service. Instead it stacks a set of insert, update and delete operations until they are submitted to the database using the Submit method on the domain service. The repository class has to support this kind of operation.
Note: If you are going to work with repository classes, make sure that either the underlying data-access technology supports a unit-of-work pattern. Of course you can also make the repository in such a way that it implements the unit-of-work pattern instead of the underlying data-access technology used.
To reduce the amount of duplicated code I’ve created a base class structure for the repository classes in the application that looks like this:
The repository design is made out of two parts, a interface IDataRepository<T> and an abstract DataRepository<TEntity,TDataContext> class. The important bits are in the DataRepository class which implements some basic insert, update and delete operations. It also exposes a DataContext which can be used when adding finder methods to the repository that are responsible for delivering IQueryable<T> resultsets. In the following table you can find all the operations on the DataRepository class.
Queries the datastore for data of type T. This is a so-called finder method of which you can create several more to meet the specific requirements of your application.
Posts the specified entity for insert. Does not perform the insert immediately.
Posts the specified entity for update. Does not perform the update immediately
Posts the specified entity for deletion. Does not perform the delete immediately
Commits previously posted changes to the database.
Implementing and testing the repository class
The implementation of the repository class is mostly done by creating a new repository class that derives from the DataRepository class. As an example I’ve implemented a ProductRepository class with a basic finder method on it.
Implementing and testing a finder method
The finder method on the ProductRepository class is responsible for finding a set of products from a particular category. The implementation of the method is pretty straightforward:
The DataContext is asked for the Products table and the records in this table are filtered so that only the products are returned that are part of the requested category. The result is returned straight to the caller.
Note: What you can’t see here is that the domain service which uses the repository can add additional filters and ordering clauses to the query when you use this implementation.
To test the method you will need a database with a setup that contains one category and two products in that category. The implementation of the unit-test is shown below:
Implementing and testing the insert, update and delete methods
The insert, update and delete methods are implemented on the DataRepository class. The implementation of the methods are very similar so I’m going to show the insert method here.
Instead of committing the data directly, it gets added to the DataContext. By adding the change to the DataContext it will be part of the active unit of work. The active unit of work gets committed once you call SubmitChanges on the data repository. The implementation of this method is shown in the following code sample:
The unit-tests insert, update and delete methods on the ProductRepository class can be implemented using a similar technique as used for the finder method. As an example I’ve implemented a unit-test to validate the insert method in the following code sample:
Notice that I have to use the Insert in combination with the SubmitChanges method to get any usable result from the unit-test. While this makes my unit-test no longer limited to just the insert method; it still does help to check if an insert operation in combination with a submit persists the new item to the database.
Implementing the domain service
As I’ve explained earlier, the domain service is responsible for implementing the link between data in de database and the client. For the domain service to work with the .NET RIA services framework you will need to follow some conventions.
Each entity exposed by the domain service should at least have one finder method that returns IQueryable<T> and corresponding Insert(T entity), Update(T entity) and Delete(T entity) methods. So with that in mind I’ve created a domain service that implements a product catalog. The schematics of this domain service are shown below.
To keep things I’m not going to discuss all the methods in the domain service in this article. Instead I’m going to show how to complete the finder method FindProductsByCategory and show some basics on how you can test the insert, update and delete methods.
The class itself has two repository instances attached to it. These fields are initialized in one constructor and can be initialized manually in an extra constructor that accepts them as an argument. The parameterless constructor is meant for runtime operation of the service, while the parameterized constructor is used for unit-testing.
The basic implementation of the domain service is shown below:
Notice the LinqToSqlMetadataProvider. This provider is required to get the whole system working. It provides the code generator of the .NET RIA services framework to determine things like the primary key of each entity and metadata required to generate validation logic for the various entities. I’m not going to discuss the functionality of the metadata provider in this article. Check the resources section of this article for the download of the metadata provider.
Implementing and testing a finder method
The FindProductsByCategory method on the domain service does the very same thing as the equally named method on the ProductRepository. The difference is that it makes use of the ProductRepository to perform the actual select operation.
The implementation of this method is shown below:
Yep, there’s only one line code involved. Testing it however is a whole different story. All the complex logic that I’ve written in the previous sections was to make it possible to remove the database from the equation when testing the domainservice. Using the parameterized constructor I can now inject stubs for the repositories.
The unit-tests don’t use handcoded stubs, but instead rely on the functionality offered by RhinoMocks to reduce the amount of work required to build the stubs. RhinoMocks not only simplifies building the stubs, but it also allows me to validate that the domain service actually works like I want it to work. The unit-test using the stubs is shown in the following sample:
The first step in the unit-test is to create the stubs for the domain service and inject them into the domain service. After that the FindProductsByCategory method is stubbed so that it always returns a single product to the calling party.
After the setup phase of the unit-test is completed, the FindProductsByCategory method on the domain service is called. This should return a single product as I have configured on the stub. The fact that this is true is checked using the AssertWasCalled extension method on the stub.
Implementing and testing insert, update and delete methods
Again to keep things simple I’m only showing the insert method used to insert a new product. The update and delete methods are very similar in implementation though.
The insert method is very straightforward and performs two steps. First it checks if the product provided isn’t point towards a null-reference. Next it calls the repository class to insert the product into the database. The implementation of this is shown below.
To gain a 100% coverage you’re going to need two unit-tests. One to check for the happy scenario and one for the scenario where the product parameter points to a null-reference. For the sake of simplicity I’m only showing the unit-test for the happy scenario.
Implementing and testing the submit logic
When using a domainservice from within Silverlight you can call SubmitChanges on a generated DomainContext class to persist the changes to the database.
What actually happens is that a changeset object is build. This object is then used to invoke the Submit method on the domain service. When the Submit method is called with a changeset a couple of things will happen.
- The changeset is authorized using the AuthorizeChangeSet operation
- The changeset is validated using the ValidateChangeSet operation
- The changeset is executed
- Insert, Update and Delete methods are invoked
- Custom service operations are invoked
- The changeset is persisted using the PersistChangeSet operation
- If the changeset could not be persisted the ResolveChangeSet operation is called. This allows you to execute custom logic to resolve conflicts in the changeset. Once these conflicts are resolved PersistChangeSet is called again to persist the changes.
Because the service does not call the SubmitChanges method on the repositories automatically we need to implement that ourselves at the appropriate moment. The best location to call the SubmitChanges method is when the PersistChanges method is invoked. As an example you can find the PersistChanges override of the ProductCatalog service below.
To validate if the SubmitChanges method is called on all the repositories you will need a unit-test that mimics the behavior of the client by invoking the Submit method with appropriate data. The following code sample shows the unit-test:
It’s quite a construction, so I’m going to explain it piece by piece. The first thing that needs to be done is to initialize the domain service. Again we’re going to use stubs created using RhinoMocks to make validation easier. After the domain service is created you need to initialize it for either a query or a submit operation. Normally this is done by the runtime when you try invoke a method on the service. But since we don’t have the normal runtime we need to do this ourselves.
For the initialization of the domain service it’s required to have a domain service context with a service provider attached to it. The service provider is responsible for providing some generic services to the domain service. One of the things that the domainservice will ask the service provider for is the current principal.
In this unit-test I’ve injected a generic principal into the service provider which fulfills the administrator role. This is to make sure that we get past the AuthorizeChangeSet method.
Note: The internals of the DomainServiceContext class and the principal aren’t documented. You will need a tool like .NET Reflector to find out how the domain service makes use of both these classes. Talk about tearing things apart 😉
After the domain service is initialized we can finally call the submit method. I used a changeset in the unit-test that will result in a call to the Insert method on the product repository.
To verify that the domain service correctly processes we need to use the AssertWasCalled extension method on the repositories. Additionally you can verify that the Insert method is called on the product repository. I skipped here, because I’ve tested that method before using a different test.
Unit-testing authentication and authorization
Using the code of the unit-test for the submit method you can also validate the authentication and authorization requirements for the various service methods. By specifying a different principal when stubbing the IServiceProvider instance you can create a wide range of users including anonymous users.
If you change the DomainOperation enumeration value in the changeset you can also test the update and delete method.
When I started looking at testing domain service I didn’t quite realize that it was this complex. If you’re going down this path I can recommend that you first check how much time you have to build the application. If it has to be done in a hurry, don’t use the data repository from the article, but instead go for a more straightforward solution using ADO.NET entity framework and generate the domain services. Also it’s still better to focus on testing the Silverlight application than to waste precious time on unit-testing code that is generated anyway.
Testing for authorization is however always a good idea as leaving the wrong method unprotected is never a good idea. The framework does not generate this, so you are responsible for adding the right authorization attributes and for checking that all the methods that need to be protected are protected.
I think the sample provided in this article is a good example of what you can do with domain services within the .NET RIA services framework in terms of testability and custom data-access logic. I really hope that more people will start to use the framework and improve it by adding test harnesses for this, because as it currently stands unit-testing domain services simply is too complex.
Check out the following resources for more information on domainservices en related themes:
- More information on the domain service lifecycle: http://weblogs.asp.net/fredriknormen/archive/2009/12/29/wcf-ria-services-domainservice-life-cycle-and-adding-transactions.aspx
- LinqToSql metadata provider download: http://code.msdn.microsoft.com/RiaServices/Release/ProjectReleases.aspx?ReleaseId=2660