Mocking isn’t just about isolating the code under test—it’s also about creating faster and simpler tests. But, if you don’t do it right, mocking can also just generate more code for you to manage and maintain. Best practices with mocking tools ensure that all your mocks add value to your testing without adding cost.
The primary purpose of mocking in automated testing is to isolate the “code under test” (CUT). Isolating the CUT has lots of benefits but the big one is that, when a test fails, you know exactly where the problem is: It’s in the CUT. There are, of course, other reasons for creating mock objects: For example, mocking a database can substantially speed up tests by eliminating trips to the database server and mocking hardware/network devices can simplify testing while allowing you to run tests when those devices aren’t available.
Regardless of why you’re using mocks, there are some best practices you can follow to reduce the cost of mocking while also making your mocks more effective.
There are a bunch of things you need to get right before you’ll start implementing any best practices when actually creating mocks—let’s get those out of the way first. After all, many of these best practices may be beyond your control:
Assuming you have input to the decision, pick a mocking framework that plays well with both your testing framework and your development tools. Telerik JustMock, for example, which targets the .NET environment, integrates with the primary development tool in the field: Visual Studio.
Write code using current best practices or, if you’re a tester, have developers follow current best practices. These days, best practices in writing code are summed up in the SOLID principles for object-oriented development. However, it doesn’t really matter what the current style in programming is: Mocking tools are going to support current “best practices in application development” so following those best practices will always be the smart choice. So, really, this best practice should end with “…and then keep staying current.”
Write loosely coupled code or, as above, if you’re a tester, get your developers/architects to write their code that way. This best practice is independent of any “current best practices in development.” Achieving any of the goals of using mock object (isolating the CUT, eliminating time-consuming operations, simplifying testing) is considerably easier in loosely coupled applications. Though, having said that, powerful mocking frameworks like JustMock will let you mock virtually any code. But, while (with the right tools) you can mock tightly coupled code, it will take longer and cost you more than it would with loosely coupled code.
The first best practice that you have control over is: Don’t write any more mocking code than you need to. Mocking code is still “more code” and, like any other code you write, takes time both to create and to maintain. That’s all time not spent on adding functionality to applications. Write all (and only) the mocking code you need … and then stop.
The next best practice helps specify what you should mock: Only mock objects with logic. Even though a mock object may only return a value, its purpose is to replace something in your application that has logic in it and, as a result, whose impact on the CUT is … well, let’s just say, “unpredictable.”
The corollary to that practice is: Don’t mock objects that only carry values. Objects used purely to transmit data (what are called “value objects” or “data transfer objects”) can be created in your tests using the original classes.
Next, Be clear what you want the test to prove. It’s not unusual to have a CUT that calls an object, which calls another object, which then calls yet another object. If you’re creating a unit test, then you probably want to mock the first object in the chain: Mocking the first object isolates the CUT, which is your typical goal in a unit test.
However, if you’re doing an integration test, you may want to mock the last object in the chain because the goal of your test is to see what happens as the objects interact. A good mocking framework will let you do that by mocking objects not directly available from the CUT (JustMock handles this through future mocking, for example).
Keep your mocks simple: If all you need is to have the mock return a value or a configured object, just have your mock object do that (typically, this is what testers mean when they refer to a “stub”). If you need a mock object to report on what was passed to it, then configure the mock do just that (in my previous example, the mock object at the end of the calling chain in an integration test might just report what, if any, data the mock received through the chain of object calls).
If you start having your mock object do more—if, for example, you start writing code that mimics real business logic (what’s sometimes called a “simulation”) —then you’re missing the point of a mock object. You’re now not only increasing the amount of code that you’ll have to maintain and modify as your application evolves, you’re also adding to the logic associated with your application. And that raises a key question: How, exactly, do you intend to test the logic inside a mock object?
When it comes to creating a mock, Prefer declarative code. Ideally, your mocking tool will let you generate mocks by stating what you want your mock to do and then let the mocking framework take care of both creating and invoking the mock. You want to avoid mocking tools that require any sort of logic to create or use a mock—that’s just more code that you’ll need (somehow) to test.
As an example, this all the code that’s required in JustMock to mock a method called CalcCost on a static object named MyStaticObject. These two lines of code:
Mock.Arrange(() => MyStaticUtility
Now that you’ve mocked that CalcCost method, any code in the test that calls that method will automatically use that mock.
Finally, Recognize the limits of mocking. Eventually, testing against a mock object isn’t going to prove what you want. There will, for example, come a time when you want to prove that your application works with the actual data in your database (e.g., during user acceptance or smoke tests). To quote Vince Gill, “They quit playing Elvis. They’re going to quit playing you.” There will be tests that, for those tests to be useful, can’t use mocks.
Your goal is to have every mock you create contribute to your tests’ value without adding to your maintenance burden. These best practices ensure that every mock you create (and every one you don’t create) does exactly that.
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter also writes courses and teaches for Learning Tree International.
Subscribe to be the first to get our expert-written articles and tutorials for developers!
All fields are required