Writing testable code
If you are new to testing, this is an important concept to understand.
Legacy codebases often suffer from the fact that they are inherently untestable from a unit test point of view. The only way to test such code without refactoring, is integration testing, where possible, and End to End testing.
Dependencies and tight coupling
In order to write proper units tests, your components should be “loosely coupled”. Broadly speaking, this means that a function or method should not depend on a another component.
Very crudely speaking, this means that a Component A
should not instantiate another component Component B
using the new
keyword.
Instead, you would inject Component B
into Component A
in the class constructor, as a method parameter or using a Dependency Injection framework.
Problem - Tightly coupled code
To help visualise this, let’s take a simple example. To keep things clear, we’ll have everything in a single file. This is a simulation of a REST API where the controller gets a userID and asks a service to process it. The service in turn asks a database/repository function to get the user name from the database.

Instantiation of the user service happens in the
userController()
method. This makes theUserService
class a hard dependency ofuserController()
.Instantiation of the database service happens in the
getUser()
method. This makes theUserDB
class a hard dependency ofuserService()
.
The above code works just fine. But it’s not “testable”.
We cannot write a unit test for the userController()
method as a unit as it depends on UserService. The same applies to the user service method getUser()
.
Solution - Loose coupling
The solution to this problem is to inject the dependencies.
It is generally considered good practice to adhere to the SOLID principles in software development. The solution in our case is the I in Solid - Inversion of Control. We don’t want to go down the SOLID rabbit hole here, as many readers will already be familiar with it and it’s beyond the scope of this discussion.
Skipping to the solution then, the answer lies in instantiating any dependencies in some central location or to use a Dependency Injection framework, and then passing those dependencies as parameters wherever they’re required.
Here’s the refactored code.

Instantiate the
UserDB
class in the controller's constructor.Instantiate the
UserService
class, passing inuserDb
as a parameter.Whenever the
userController()
method is called, it uses the previously instantiateduserService
.The
UserService
constructor is passed theuserDb
variable that was instantiated by the controller.getuserFromDB()
now usesuserDb
whenever thegetUser()
method is called from theUserService
.
Again, there are many ways to inject dependencies in practice such as Dependency Injection Frameworks, passing dependencies directly to methods and more. At this stage, the important thing to understand is the concept of loose coupling.
Why it’s now unit testable
Before
If we look at the above getuser()
method, what is it that allows us to now write a unit test when we couldn't before?

The whole purpose of a unit test is to test one unit (in this case our method getUser()
in total isolation.
However, UserDB userDb = new UserDB()
prevents us from doing this because it is going to instantiate a UserDB
object and then call its getUserFromDB()
method. This in turn will invoke a database operation.
It's very much not isolated.
After
The loose coupled version looks like this.

Here, userDb
was previously instantiated and passed into UserService
in its constructor, so when we call getUser()
, userDb
is ready to go.
But that still doesn’t really answer the question of why this is more testable.
Mocking
The answer lies in what happens when we write a test.
Mocking, made easier with Mocking Frameworks such as Mockito in Java and Moq in C#, allows us to inject a fake or dummy object whose behaviour we can fully control. Take a look at the unit test below.

First, we arrange or setup what we need to run the test. In our case, we want to create a mock of the
UserDB
class. If we were following strict TDD, we would mock against an interfaceIUserDB
rather thanUserDB
class itself. If you're doing Test Driven Development (TDD) then using an interface has the advantage that you can test before even writing the implementation of UserDB. Don't worry about this for now.Next, we act.
when(mockUserDb.getUserFromDB(any())).thenReturn(expected)
. This tells our mocking framework “whengetUserFromDB()
is called, with any parameter, fromUserService
, then intercept it and replace it with something that returnsexpected
(=”John Brook”). We can then callgetUser()
and rely on it return what we want.Finally we assert that what
userService.getUser()
returns is what we expect,expected
.
Seems like a lot of work!
In our example, where our service is not doing anything really, you could consider whether it is really worth testing. However, real world examples are rarely this simple. As soon as there is some sort of business logic in your method, there will be things that are absolutely worth testing. The small amount of effort is well worth the future gains in quality.
Conclusion
We hope we’ve helped make a bit clearer how making your code testable is the first step on the road to great quality.
DevMate helps reduce the amount of work required to generate your tests whether you’re using TDD or not. Developers can involve Domain Experts, PMs, POs and QA Engineers in defining requirements and test cases as well as generating the test code. This lets developer spend less time writing test code and more time writing functional code.