A couple weeks ago I wrote a post about unit testing and its importance in developing quality software. You can find that here. A topic that is seen a lot when discussing unit testing is the idea of “mocking” objects (I’ll explain this in a second).
I didn’t go into this in my unit testing post because I wanted to keep it simple and convey the idea that unit testing could be a valuable tool if used correctly. I didn’t want to delve too much into the complexities of some of the actual implementations details of good unit tests.
However, I received a question via email last week (something which I encourage all of my readers to do!) that said he understood the value of unit testing but was still a little confused on the benefits of using “mocks”. This gave me the idea for this post!
So let’s first rewind a little and first define what “mocking” objects really means. Mocking is “faking” an object. If you look up the noun mock in the dictionary you will find that one of the definitions of the word is something made as an imitation. Sometimes we want to switch our some of our applications dependencies with “fake” (aka mocks) objects. I’ll go into more detail why in a second but let’s first get something else straight.
Mocking goes hand in hand with the concept of unit testing and Separation of Concerns (SoC). We already talked a little bit about unit testing the idea of SoC is a little different. Let me go into a little more detail to but still take a pretty high-level approach.
Formally, SoC is a design principle for separating a computer program into distinct sections, such that each section addresses a separate concern.
Concerns are the different aspects of software functionality. For instance, the “business logic” of software is a concern, and the interface or view through which a person uses this logic is another.
The separation of concerns is keeping the code for each of these concerns separate. Changing the interface/view should not require changing the business logic code, and vice versa. Model-View-Controller (MVC) design pattern is an excellent example of separating these concerns for better software maintainability.
So now that you are an expert in SoC, how does SoC and unit testing go together? Well there are a few principles they all good unit tests follow and that is that they are:
- Automatic : Invoking of tests (as well as checking results for PASS/FAIL) should be automatic
- Thorough: Coverage; Although bugs tend to cluster around certain regions in the code, ensure that you test all key paths and scenarios.
- Repeatable: Tests should produce the same results each time…every time. Tests should not rely on uncontrollable params.
- Independent: Very important.
- Tests should test only one thing at a time. Multiple assertions are okay as long as they are all testing one feature/behavior. When a test fails, it should pinpoint the location of the problem.
- Tests should not rely on each other – Isolated. No assumptions about order of test execution. Ensure ‘clean slate’ before each test by using setup/teardown appropriately
- Professional: In the long run you’ll have as much test code as production (if not more), therefore, follow the same standard of good-design for your test code. Well factored methods-classes with intention-revealing names, No duplication, tests with good names, etc.
- Good tests also run Fast. any test that takes over half a second to run.. needs to be worked upon. The longer the test suite takes for a run.. the less frequently it will be run. The more changes the dev will try to sneak between runs.. if anything breaks.. it will take longer to figure out which change was the culprit.
Each of these are very important, but two areas stand out to me as the most important of all — Repeatable and Independent. Mocks help you achieve developing unit tests that are repeatable and independent and follow SoC. Let’s dive a little deeper.
An object under test may have dependencies (a direct violation of good unit test from above!) on other (complex) objects. To fix this and to isolate the behavior of the object you want to test you replace the other objects by mocks that simulate the behavior of the real objects. This is useful if the real objects are impractical to incorporate into the unit test.
In short, mocking is creating objects that simulate the behavior of real objects. The true purpose of mocking is to achieve real isolation.
Let’s go through an example. Say you have a class CustomerService, that depends on a CustomerRepository. You write a few unit tests covering the features provided by CustomerService. They all pass.
A month later, a few changes were made, and suddenly your unit tests start failing – and you need to find where the problem is.
A logical person would assume “Hey, my unit tests for class CustomerService are failing. The problem must be there!”
However, because you introduced a dependency (CustomerRepository) the problem could actually be there! If any of a classes dependencies fail, chances are the class under test will fail too.
Now picture a huge chain of dependencies: A depends on B, B depends on C , C depends on D. If a fault is introduced in D, all your unit tests will fail.
And that’s why you need to isolate the class under test from its dependencies (may it be a domain object, a database connection, file resources, etc). You want to test a unit.
Let’s look at some further obstacles that could be introduced without using mocks. Consider testing a large web application. Let’s assume that the unit tests use no mocks at all. What problems will it face?
Well, the execution of the test suite will probably be very slow: dozens of minutes — perhaps hours. Web servers, databases, and services over the network run thousands of times slower than computer instructions; thus impeding the speed of the tests. The cost of testing just one statement within a business rule may require many database queries and many web server round-trips.
The tests are sensitive to faults in parts of the system that are not related to what is being tested. For example: Network timings can be thrown off by unexpected computer load. Databases may contain extra or missing rows. Configuration files may have been modified. Memory might be consumed by some other process. The test suite may require special network connections that are down. The test suite may require a special execution platform, similar to the production system.
Now before you go mocking everything, step back and decide what actually needs to be mocked and where you might just be adding complexity to your project and code. As always, your mileage may vary, and depending on what you are developing you may be able to have perfectly okay unit tests with some of the classes having minor dependencies (especially if these dependencies rarely changes).
My guideline for mocking when you are just getting started is to mock across architecturally significant boundaries (database, web server, and any external service), but not within those boundaries.
Of course, this is just a guideline and shouldn’t be applied to every project, but I believe it is a good starting point.
I hope this clears up what mocks are, why we use them, and how they can be beneficial. If you have any questions, please leave them in the comments!
If you liked this post, please share it with others! That is the biggest compliment I could receive. Also, please subscribe to my blog (jasonroell.com) if you are a technology enthusiast! Have a great day and learn on!
If a test uses some default values which are saved in some config file in the application, would the test data directly access the config file or would you expect the test to have hard-coded values so that the test fails if the config data changes?