- create unit tests with mocked dependencies
- create integration tests with real dependencies (typically - DB)
Why service layer is difficult to testThe service method usually consists of three parts:
- Load some data from DB.
- Process data from DB and method's arguments.
- Save results to DB and/or return result from method.
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 classLet'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 practicesDoes it look familiar to you? This idea is nothing new in the land of software development. You can think of it as:
- 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.
- Removing dependencies on external libraries/frameworks - dependency-free classes are easier to test, because there's less chance that they contain test-unfriendly features.
- 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.