2015-08-02

Writing more testable code - a simple trick

While writing tests for service layer you can:
  • create unit tests with mocked dependencies
  • create integration tests with real dependencies (typically - DB)
Both solutions have their pros and cons and neither is 100% convenient. This post shows an alternative approach.

Why service layer is difficult to test

The service method usually consists of three parts:
  1. Load some data from DB.
  2. Process data from DB and method's arguments.
  3. Save results to DB and/or return result from method.
Service layer is difficult to test because you have to either mock service's dependencies or prepare input data in DB and check service result by running SELECT in DB.

This means that testing is difficult because of points 1 and 3, but the logic that we want to test is actually in point 2.

Extract business logic to a testable class

Let's refactor service method and extract point 2 to another class.

That new class contains only processing logic. It does not load data from DB. Actually it doesn't even know or care where input data came from - it just receives it as arguments. Similarly it simply returns something as its result and does not care about saving it to DB.

This makes that class very easy to test. No mocking or DB setup is required. We just have to create input data (Java objects) and assert on method's result.

Similarity to other best practices

Does it look familiar to you? This idea is nothing new in the land of software development. You can think of it as:
  1. Applying SRP - the service described at the beginning of the post violates SRP as it communicates with DB and executes business logic. After refactoring the new classes adhere to SRP and thus are easier to test.
  2. Removing dependencies on external libraries/frameworks - dependency-free classes are easier to test, because there's less chance that they contain test-unfriendly features.
  3. Using concepts of functional programming - the extracted class operates only on input data and its only result is the returned object (no side effects). This has already been described here.
To sum it up, there's no magic here, it's just applying good software development practices.