
Introduction
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.
Repository structure
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.
Domain operation |
Description |
Query<T>(param1,param2) |
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. |
Insert(T entity) |
Posts the specified entity for insert. Does not perform the insert immediately. |
Update(T entity) |
Posts the specified entity for update. Does not perform the update immediately |
Delete(T entity) |
Posts the specified entity for deletion. Does not perform the delete immediately |
SubmitChanges |
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:
1: /// <summary>
2: /// Finds all products in the product category with the specified ID
3: /// </summary>
4: /// <param name="categoryId">ID of the product category</param>
5: /// <returns>Returns the found products</returns>
6: public IQueryable<Product> FindProductsByCategory(long categoryId)
7: {
8: return DataContext.Product.Where(
9: product => product.ProductCategoryId == categoryId);
10: }
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:
1: [TestMethod]
2: public void FindProductsByCategoryExistingCategory()
3: {
4: long categoryId = 2;
5:
6: ProductRepository repository = new ProductRepository();
7: var result = repository.FindProductsByCategory(categoryId);
8:
9: Assert.IsNotNull(result);
10: Assert.AreEqual(2, result.Count());
11: }
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.
1: /// <summary>
2: /// Inserts a single entity in the database
3: /// </summary>
4: /// <param name="entity">Entity to insert</param>
5: public void Insert(TEntity entity)
6: {
7: _context.GetTable<TEntity>().InsertOnSubmit(entity);
8: }
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:
1: /// <summary>
2: /// Commits the changes to the database
3: /// </summary>
4: /// <param name="changes">The changeset to commit</param>
5: public void SubmitChanges()
6: {
7: // Use read-commited isolation level.
8: // Kill the transaction after 30 seconds if not commited
9: TransactionOptions options = new TransactionOptions
10: {
11: IsolationLevel = IsolationLevel.ReadCommitted,
12: Timeout = TimeSpan.FromSeconds(30)
13: };
14:
15: // Commit the changes to the database
16: using (TransactionScope scope = new TransactionScope())
17: {
18: _context.SubmitChanges();
19: scope.Complete();
20: }
21: }
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:
1: [TestMethod]
2: public void InsertProductReturnsInsertedProduct()
3: {
4: ProductRepository repository = new ProductRepository();
5: Product newProduct = new Product
6: {
7: Name = "Test product",
8: Price = 12.95M
9: };
10:
11: repository.Insert(newProduct);
12: repository.SubmitChanges();
13:
14: var foundProduct = repository.Query().FirstOrDefault(item => item.Name == "Test product");
15:
16: Assert.IsNotNull(foundProduct);
17: Assert.AreEqual("Test product", foundProduct.Name);
18: Assert.AreEqual(12.95M, foundProduct.Price);
19: }
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:
1: [EnableClientAccess()]
2: [LinqToSqlMetadataProvider(typeof(SilverlightShopDataContext))]
3: public class ProductCatalog : DomainService
4: {
5: #region Private fields
6:
7: private TransactionScope _transactionScope;
8: private IProductCategoryRepository _categoryRepository;
9: private IProductRepository _productRepository;
10:
11: #endregion
12:
13: #region Constructors
14:
15: /// <summary>
16: /// Initializes a new instance of the ProductCatalog class.
17: /// </summary>
18: public ProductCatalog()
19: {
20: _productRepository = new ProductRepository();
21: _categoryRepository = new ProductCategoryRepository();
22: }
23:
24: /// <summary>
25: /// Initializes a new instance of the ProductCatalog class.
26: /// </summary>
27: /// <param name="categoryRepository"></param>
28: /// <param name="productRepository"></param>
29: public ProductCatalog(IProductCategoryRepository categoryRepository, IProductRepository productRepository)
30: {
31: _categoryRepository = categoryRepository;
32: _productRepository = productRepository;
33: }
34:
35: #endregion
36: }
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:
1: /// <summary>
2: /// Finds all products in the product category with the specified ID
3: /// </summary>
4: /// <param name="categoryId">ID of the category</param>
5: /// <returns>Returns the found products</returns>
6: public IQueryable<Product> FindProductsByCategory(long categoryId)
7: {
8: return _productRepository.FindProductsByCategory(categoryId);
9: }
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:
1: /// <summary>
2: /// Finds a set of products from an existing product category.
3: /// This should return a list of products from the database.
4: /// </summary>
5: [TestMethod]
6: public void FindProductsByCategoryExistingCategoryWithProducts()
7: {
8: IProductRepository productRepository = MockRepository.GenerateStub<IProductRepository>();
9: ProductCatalog catalog = new ProductCatalog(null, productRepository);
10:
11: long categoryId = 1;
12:
13: productRepository.Stub(repository => repository.FindProductsByCategory(categoryId))
14: .Return(Enumerable.Repeat(new Product(), 1).AsQueryable());
15:
16: var result = catalog.FindProductsByCategory(categoryId);
17:
18: Assert.IsNotNull(result);
19: Assert.AreEqual(1,result.Count());
20:
21: productRepository.AssertWasCalled(repository => repository.FindProductsByCategory(categoryId));
22: }
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.
1: /// <summary>
2: /// Inserts a new product in the product catalog
3: /// </summary>
4: /// <param name="newProduct">Product to insert</param>
5: [RequiresAuthentication]
6: public void InsertProduct(Product newProduct)
7: {
8: if (newProduct == null)
9: throw new ArgumentNullException("newProduct", "newProduct is null.");
10:
11: _productRepository.Insert(newProduct);
12: }
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.
1: /// <summary>
2: /// Inserts a new product with valid input
3: /// </summary>
4: [TestMethod]
5: public void InsertProductWithValidInput()
6: {
7: IProductRepository productRepository = MockRepository.GenerateStub<IProductRepository>();
8: ProductCatalog catalog = new ProductCatalog(null, productRepository);
9:
10: Product newProduct = new Product();
11:
12: catalog.InsertProduct(newProduct);
13:
14: productRepository.AssertWasCalled(repository => repository.Insert(newProduct));
15: }
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.
1: protected override bool PersistChangeSet(ChangeSet changeSet)
2: {
3: _productRepository.SubmitChanges();
4: _categoryRepository.SubmitChanges();
5:
6: return base.PersistChangeSet(changeSet);
7: }
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:
1: [TestMethod]
2: public void SubmitInsertProductAuthorizedAccess()
3: {
4: IProductRepository productRepository = MockRepository.GenerateStub<IProductRepository>();
5: IProductCategoryRepository categoryRepository = MockRepository.GenerateStub<IProductCategoryRepository>();
6: ProductCatalog catalog = new ProductCatalog(categoryRepository, productRepository);
7:
8: ChangeSetEntry insertProductEntry = new ChangeSetEntry(1, new Product(), new Product(), DomainOperation.Insert);
9: IEnumerable<ChangeSetEntry> entries = Enumerable.Repeat(insertProductEntry, 1);
10: ChangeSet changeSet = new ChangeSet(entries);
11:
12: // Initialize the domain service for a submit operation
13: IServiceProvider serviceProviderStub = MockRepository.GenerateStub<IServiceProvider>();
14: DomainServiceContext domainServiceContext = new DomainServiceContext(serviceProviderStub, DomainOperationType.Submit);
15:
16: // The domain service will ask the service provider for an IPrincipal instance.
17: // In this unit-test an anonymous user is returned.
18: serviceProviderStub.Stub(provider => provider.GetService(
19: typeof(IPrincipal))).Return(new GenericPrincipal(
20: new GenericIdentity("TestUser"),
21: new string[] { "Administrator" }));
22:
23: // Initialize the productcatalog with the correct settings
24: catalog.Initialize(domainServiceContext);
25:
26: // Invoke the submit operation
27: catalog.Submit(changeSet);
28:
29: // Validate that the repositories are called
30: productRepository.AssertWasCalled(repository => repository.SubmitChanges());
31: categoryRepository.AssertWasCalled(repository => repository.SubmitChanges());
32: }
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.
Conclusion
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.
Resources
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
3 comments
Hello,
Thanks for this good article.
Is there any chance to have some code sample?
Thanks.
Adriano Labate
Hi Adriano,
Sorry, I don’t have any additional code with this article. But it should be pretty easy to start writing the components mentioned. I haven’t put in anything really special apart from the Repository classes.
willemm
Very nicley written. In my work i was trying not only to unittest the domainservice but rather reuse validations etc from console applications.(that don’t create changesets, ) I ended up with a solution that lets the domainservice class makes calls to a servicefacade that in turn uses (Entity Framework) reposistories. Wish i had read your post prior to all time spent figuring out a solution. 🙂
What would be your approach for reusing dataannotation validation and authorization from a console app ?.
Per Hubinette