-
Notifications
You must be signed in to change notification settings - Fork 15
Which tests to automate as Unit and Integration Tests?
This page lists some considerations when deciding which Unit Test and Integration Tests to create for automatically testing newly added or updated functionality. These considerations focus on the 121 Service.
-
Deciding which automated tests to create is more of an art than a science. Hence, these considerations should not be seen as hard rules.
-
Write automated tests that are valuable. In that: the value of an automated test should outweigh the costs of writing and maintaining them.
-
The idea is that automated tests will catch defects early, as to lower the costs of reporting, finding and fixing these defects, and to mitigate the negative consequences a late defect can have in production.
-
The costs of writing and maintaining an automated test should be lower than the risk of not having this automated test. The risk being: the chance of defects going undetected times the impact of these defects.
-
We are not going to take the effort into calculating monetary numbers with the value and costs, as that effort will be too high. Another reason why this is more an art than a science.
-
-
Unit Tests: provide breadth by covering a wide range of possible responses (both normal and edge cases) in isolation using mocks. These tests are fast and ensure that your function handles responses correctly.
-
Goal: Focus on how your function behaves when it receives different types of responses from the API. You are testing the logic inside your function in a controlled, isolated environment.
-
Focus: Response handling, business logic, and edge case scenarios.
-
Benefit: Fast and reliable since you're mocking external dependencies.
-
-
Integration Test: Provide depth by testing real-world behavior with actual API calls. These tests ensure that your application can successfully integrate and handle real-life conditions, but they should be more selective and focused on core functionality and key error scenarios.
-
Goal: Verify that your application can actually communicate with the real API and handle its responses correctly in a real environment.
-
Focus: Actual API calls, request formatting, response structure, and overall interaction with the external service.
-
Benefit: High confidence that the integration works in real-world conditions, but can be slower and more brittle.
-
-
There will be some overlap in the scenarios tested by unit and integration tests, but the goals differ.
-
Unit tests (with mocks) focus on isolated response handling and can cover a wide range of scenarios, including edge cases.
-
Integration tests focus on real interactions with the API and are typically reserved for key paths and real-world conditions.
-
The combination of both approaches gives you confidence in both your code's behavior and the real-world integration, while avoiding unnecessary duplication and redundancy.
-
-
It is not always valuable to write Unit Tests, so take the Testing Pyramid lightly. See this article about the Test Trophy for background information. Situations where Unit Tests can have value: (a) when it makes sense to test a lot of "edge cases" of a function, like "weird input", (b) when functions have internal "business logic" (let's simply say: "ifs", or "several paths").
-
When writing Unit Tests turns out to be a complex undertaking, this is a "code smell". The unit is probably too big or complex. Try to refactor the unit into smaller units, then each unit can be tested easier:
-
Refactor (relatively) independent blocks of business logic into their own (private) functions: abstract, encapsulate and adhere to the SRP (Single Responsibility Principle).
-
Refactor independent stand-alone private functions into a separate "Helper" Service. These functions then become public and can be tested in their own unit test file. The Helper Service is typically not exported from the Module, so does not cause added complexity for users of the Module. The functions in the Helper Service should in principle only do something with the input paramters, and return something, and not have any external dependencies. Example: how we refactored functions from the SafaricomApiService into the SafaricomApiHelperService with its own Unit Test file.
-
See if any of these stand-alone independent functions are concern more generic functionality that may be valuable to other Modules as well. If so, refactor to a helper service or method with then its own unit test file. Example: how we refactored validating the authentication token into a generic TokenValidationService with its own Unit Test file, also refactoring the IntersolveVisaApiService to use it.
-