Quality is an essential part of everything that we do at SynTraffic. In our effort to have high code quality, we had very early on embraced Test Driven Development (TDD) for building software components. As our code was a healthy mix of different technologies and frameworks, we had our share of experiences in applying TDD and effectively writing TDD tests. Over the next few blog posts, we wanted to share our learnings and best practices for writing effective unit tests.
What is Mocking?
Mocking is a technique that allows unit testing of your code by simulating external dependencies. A typical scenario is when the block of code you would like to unit test consumes external resources (database, network). In such cases, you would typically create mock objects which mimic the behavior of the real object (database, network) where your client object remains unaware if the external resource is a real or a mocked object.
In the scenario below, (A) is your real-world login action performed against a RealDataStore (implements the interface IDataStore). Scenario (B) is your unit test, that sets up a MockDataStore (also implements the interface IDataStore) and interjects it in your application FooBar, before calling the Login Action. By doing this, we have ensured that the application code paths remain the same for both real-world as well as our unit tests, thereby validating the behavior of the application using our unit tests.
One key point to note is that in the real-world scenario the RealDataStore sits on a different machine (Mz) than the application (Mx). In the mock scenario, MockDataStore is on the same machine (Mx) and running in the same process as the application.
Here are a few reasons you would want to mock:
1) Repeatable/Reliable: It makes your test repeatable. When your tests use external resources such as database or any other network operation, and your “successful” unit test starts to fail, you now end up debugging multiple places to understand the issue. Often times these failures happen due to transient errors which now make your test non-repeatable and unreliable. By mocking the external dependencies and keeping everything within the process, you make your tests deterministic.
2) Higher Test Coverage: Mocking allows you validate your code in the hard to test/repro scenarios like database connectivity failure, disk failure, out of disk space exceptions by simulating them locally. You can also setup any given state of the application/ database/external source. For example you can setup a real world scenario of a disk failure or database failure which is otherwise impossible to setup in a unit test. Now you can validate if your application responds as expected in these scenarios, thereby increasing your test coverage.
3) Improved Efficiency: You get efficiency gains as your unit tests can now run faster without waiting on large, unwieldy resources like a database. This way you will encourage the culture of running the tests after each check-in, thereby catching bugs early and help ship a quality product.
4) Faster Evolution: As we embrace agile, the product is evolving constantly and so follows the features, design and the code. As the unit tests serve as the gatekeeper of the current state of your product design and code, you can refactor the code with greater ease and that helps your evolve faster.
In our next blog post, we will talk about How to Mock?