Sunday, November 23, 2014

Property Injection - Additional Unit Tests

I really like to talk about Dependency Injection (whether in live presentations or on Pluralsight). I recently got a question about how to do additional testing on the ServiceRepository object.

Here's the question:
...Can you share some code that would unit test the AddPerson method in the ServiceRepository? What I've been trying to do on my own unit test of AddPerson is to:
In test setup:
1. Create a dummy list with two Person objects
2. Use Moq to mock up the IPersonService and setup GetAllPeople to return the dummy list
In the AddPeople Unit Test:
3. Create a new Person object
4. Create an instance of the ServiceRepository
5. Set the ServiceProxy property of the ServiceRepository to the mocked IPersonService
6. Call the GetAllPeople which will presumably return the dummy list
7. Assert that there are only 2 Person objects in the list
8. Call AddPerson method on the ServiceRepository
9. Assert that there are now 3 objects in the dummy list
Is this the right approach or am I completely off target?
When I first looked at the question, this looked like the approach that I would take. But as I've said before, I'm a bit of a slower thinker. After some more thought, I came up with a slightly different approach.

The Existing Tests
Let's look at an existing test. This code can be downloaded here: Dependency Injection - A Practical Introduction.

Here's the setup:

The primary objective of this code is to create a mock object of our "IPersonService" (our SOAP service) that we can use when we are testing the "ServiceRepository" object. We're using Moq for this. The short version is that when someone calls the "GetPeople" method of the mock service, we would like to return the "people" list that has our 2 test records.

And here's an existing test for the "GetPeople" method:

This uses property injection to inject the "IPersonService" into the repository through the "ServiceProxy" property. We talked about property injection in detail in a previous article, so we won't go into that again here. As we can tell from our assertions, we're expecting to have 2 records returned by our method call (the same 2 test records we created in the setup).

Testing the AddPerson Method
To test the "AddPerson" method of the repository, we want to verify that the "AddPerson" method on the *service* is called. This is a slightly different approach than was proposed above, and we'll take a look at why I chose this path in just a bit.

In order to do this verification test, we're going to take advantage of a feature in Moq. But if we want to use this in our tests, we need access to the mock object itself in the test (which we don't currently have).

Here's our updated test setup:

Notice that we've changed the class-level variable. Instead of "IPersonService _service", we have "Mock<IPersonService> _serviceMock". The rest of our setup code is mostly the same. We're still mocking up the "GetPeople" and "GetPerson" methods. And notice that we haven't added anything special for the "AddPerson" method to the mock object.

Update the Existing Tests
Because we changed our class-level variable, we need to also modify the existing tests:

The difference is in the second line of our test. Instead of assigning "_service" to the "ServiceProxy" property, we assign "_serviceMock.Object". This pulls the "IPersonService" object out of the mock (and this is what we were doing in the original test setup to assign the class-level variable).

The AddPerson Test
So let's take a look at how we will test the "AddPerson" method:

Let's walk through this. In the "Arrange" section, we create an instance of the repository, inject the mock service through the property, and then create a new Person object to use as a parameter.

In the "Act" section, we call the "AddPerson" method with the new Person object. When the "AddPerson" in the repository is called, we expect that the "AddPerson" method in the service will be called.

And in the "Assert" section that is exactly what we check for. Our mock object has a "Verify" method that let's us check to see what methods were called, what parameters they are called with, and how many times they are called.

In this case, we want to verify that "AddPerson" was called on our mock object with the "newPerson" parameter. The "Times.Once()" option specifies that we expect this method was called one time (and only one time). There are lots of other cool options such as "Times.AtLeastOnce()" and "Times.Never()").

We can use this same approach to test our update and delete methods.

Approaches to Testing the Add Method
This approach is a bit different than was proposed in the question. That proposal included calling "AddPerson" and then calling "GetPeople" to verify that the new person was added to the data.

But we didn't check this at all. Why not?

Well, this has to do with what object we are testing. If we were testing the service (i.e. the data store), then we would want to verify that the new Person was actually persisted to our data storage layer. But we aren't testing the service here, we are testing the repository.

To test the repository, we want to make sure that we make the proper calls into the service, but we don't need to check that the service is doing what we expect (at least not here -- hopefully we have separate tests to verify the service does what we expect).

Why Did We Test the GetPeople Method Differently?
When we tested the "GetPeople" method, we created test data and configured the mock object to return that data. This is a different level of effort than we went through for "AddPeople". The reason is that "GetPeople" has a return value. In order to check the return value, we had to mock up the object to return a test value.

But the "AddPeople" method on the repository returns void. This means that we do not need to mock up a return value. Instead, we expect some sort of action to be done. In this case, we expect that the action is calling the "AddPeople" method of the service (which also returns void). So all we need to do is verify that the "AddPeople" call is made on the service.

A More Complex Approach
There is another approach that we could take. Instead of using a mock object, we could create a test object. This is simply a test class that implements the "IPersonService" interface. This class could have the test data built in to an internal list, and the CRUD operations could operate on this data.

So the "AddPerson" method would add a record to the test data, and "GetPeople" would return all of the test data including the new record.

This seems like a pretty reasonable approach. But with unit tests, we're testing pieces of code in isolation. As mentioned above, if we check the "GetPeople" method to make sure we get the new record, we're really testing the data storage part of the application (as opposed to just the repository part).

Wrap Up
There are a lot of different approaches to unit testing. So if you are using a full test object, don't feel like you're "doing it wrong". I take a practical approach: if the tests are valid, usable, and understandable, then use whatever works best for you.

In my experience, I've been successful with paring down unit tests to small pieces of functionality. In addition, I only test the publicly-exposed parts of my classes. If I feel the need to test an internal member, then I usually take that as a sign that I need to split out the functionality into another object. But that's a story for another day.

I have found unit tests to be invaluable in my workflow of building applications -- especially since I generally deal with applications with interactive user interfaces. The more things I can test, the less frequently I need to run through the UI of the application to test features, and the more confidently I can proceed with my coding.

Happy Coding!

No comments:

Post a Comment