One of the aspects of writing code is to make it testable. If it can’t be tested, it can’t be trusted. Unit testing has been there for ages – but it’s still a tough job (at least for me ;) As far as this blog is concerned, I’m planning to put together “basic” details of unit testing and mocking.
One would call a test, “Unit Test” if a function is tested in isolation, without considering its dependencies. I know many of us have seen tests that go all the way to the database or a service. These are NOT unit tests. You may call them Integration tests.
Dependency - Almost always there are no functions in isolation. Every function uses objects of other classes and uses properties and functions on those objects.
Consider below scenario,
Customer Business Logic is using ICustomerDL (one of its concrete implementations) for persistence of Customer string. In my “real application” that Concrete implementation is CustomerDL. Hence it’s the dependency for functions in CustomerBL - GetCustomerString() or SaveCustomerString()
What do I want to achieve? – say test logic in SaveCustomerString() only – not the function it calls in-turn. Design is to call GetCustomerString() in DL. What logic you might have in CustomerBL? Just for the sake of example, I put it in some logic in SaveCustomerString(). Consider below flowchart.
So we want to test this logic and this logic alone. That means unit tests would be written to assert a) if persist is successful, would function return true? b) if it failed with a system error return false? c) if it failed due to certain other error would it retry etc.
Mock or Fake or Stub dependencies– As mentioned earlier (Figure 1), the real application has CustomerDL (concrete implementation) from ICustomerDL. If I replace that with some dummy implementation, which is “hardcoded” (not exactly – but will talk about it) to give me specific responses, I can just focus on logic in CustomerBL. So Mock or Fake or stub dependencies.
Fake – simple, hard coded or any other form of shortcut for returning specific responses.
Stub – take a step forward and provide canned answers
Mock – Take another step forward and program responses to specific requests.
Frameworks and tools have evolved quite a bit, but there is still no silver bullet/one solution that fits all the problems. This is true especially in mocking frameworks.
I would like to focus on mocking. I use this more often than stub or fake.
Before we get into frameworks, quick mention about another design principle -
Dependency Injection - Irrespective unit testing or not we want our classes to be “loosely coupled”. And hence it’s a good practice to introduce Dependency Injection – “using class”/client decides which dependency to be used for a given class.
The pattern prescribes to use interface reference in a give class. Object of concrete implementation (derived class) is set externally by the "using class"/client.
Concrete class may be set/passed-on through a parameterized constructor - Constructor Injection; Through function parameter - method Injection; Through a public property exposed - Property Injection/Setter Injection.
Dependency Injection of concrete implementation can be automated and configured. So a container can inject dependencies based on configuration - allowing dependent objects be changed dynamically. For more refer to Unity or Spring.net
Moving on to Unit Tests, DI helps mocking. Mocking frameworks help program specific responses, so that we test logic in given functions. Below is a MOQ example of mocking dependencies. Hopefully comments are easy enough to understand what’s being done.
Couple of other useful and easy to implement mocking frameworks – nSubstitute, Rhino Mock.
Slightly different category is Moles (Fakes in Visual Studio 2012), which lets you replace the mock/fake at IL level. This framework helps you mock static functions, which is not possible by the other frameworks mentioned above. In my view, this is important. Many times Data Access Layers and things similar to it are static.
First, let’s look at above mentioned (figure 3) MOQ test equivalent in Moles (in figure 4 below).
Now let’s look at static function mocked,
That’s all cool; I guess the real problem starts if you want to eliminate dependencies of functions that are not in your control.
- One approach is to Wrap them in a different class. For frameworks that don't work at IL level, this is a very useful approach. Functions that are required to be mocked either should be virtual or derived from an interface. But it could get tedious if there are lot of external DLLs.
- Moles let you mock many functions directly, at IL level. Consider example in Figure 6. It mocks Stream Reader
Below are the tests mentioned in "What do I want to achieve section above" -
Some general occurrences/confusions:
- Mockery Anti-pattern: As you search around you will quite a number of people have fallen for this anti-pattern. It’s a case where one would write tests on mock. Mocks are for dependencies. It’s not real code. It’s not part of application and hence doesn’t make sense to test mocks.
- Don’t try to design unit tests too much – I saw instances tests are fairly well modularized and certain functions reused. If we write a common function to return mocks, as you change a specific test and hence had to change the mock, need to verify series of test methods that might be impacted. This is not desirable. My belief is self-contained tests are easy to maintain.
- InternalsVisibleTo attribute – Many dependencies are internal (access modifier). Unit tests are organized generally in a different project. So internal dependencies are not accessible. One hack to solve this conflict is by using InternalsVisibleTo and specify name of the Test assembly. This allows internal class accessible the test project. Consider solving this problems using Accessor classes as well.
- Need to test private functions – I would choose to test private functions as well. Yes testing public functions is more important. But private functions are used as well. Would it be okay to not test code that’s possible to change in future? Can the logic in private functions be ignored? I guess no.
No comments:
Post a Comment