🤔 Can a Service Test Itself? A Deep Dive into Runtime Test Execution in Spring Boot
1. The Question That Sparked It All
While working on a Spring Boot project, a curious idea crossed my mind:
"Can a live, running microservice automatically test itself — including executing JUnit/Mockito-based unit or integration tests — and return the results via an API?"
At first glance, it sounds both innovative and straightforward. Imagine hitting an endpoint like /run-tests, and seeing all your tests execute, with a JSON result showing how many passed or failed. Neat, right?
But, as we dig deeper, we realize that this seemingly simple idea unravels a chain of limitations, design caveats, and some deep JVM internals.
2. Why Typical Unit Tests Don't Work at Runtime
Here’s the catch: JUnit and Mockito-based tests are not part of the runtime execution of a Spring Boot application. This includes:
@RunWith(SpringRunner.class)
@SpringBootTest
@MockBean
These annotations and features rely on the Spring Test lifecycle, which:
❌ What Happens If You Try It?
Suppose you expose a REST endpoint like this:
@GetMapping("/run-tests")
public ResponseEntity<Map<String, Object>> runTests() {
Result result = JUnitCore.runClasses(MyTestClass.class);
// Build and return the result map...
}
Now consider your test class:
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTestClass {
@Autowired
SomeService someService;
@Test
public void testSomething() {
assertNotNull(someService);
}
}
🔥 Boom. It fails. You’ll encounter errors like Could not initialize class or NullPointerException — because Spring context was never loaded.
MockServer, @Autowired components, and test configs all fail.
3. The Limited but Working Approach: JUnitCore for Plain Tests ✅
Despite the limitations, you can run plain JUnit tests from within a live service using:
Result result = JUnitCore.runClasses(SimpleTest.class);
Where SimpleTest looks like:
public class SimpleTest {
@Test
public void sanityTest() {
assertEquals(2, 1 + 1);
}
}
This works because:
But as you’ve already guessed — no autowiring, no MockServer, no Spring BootTest features.
4. The Alternative: Build a Test Orchestrator Microservice 🤖
What if we separate the concern of testing into its own service?
Let’s say we write a Test Runner Service that runs all JUnit/Spring Boot tests for any microservice it manages. This service can:
🧪 Example: Triggering Tests Externally
@GetMapping("/run-spring-tests")
public ResponseEntity<String> runSpringTests() throws IOException, InterruptedException {
ProcessBuilder builder = new ProcessBuilder("mvn", "test", "-Dtest=SimSwapDateApplicationControllerTest");
builder.directory(new File("/path/to/your/project"));
builder.redirectErrorStream(true);
Process process = builder.start();
StringBuilder output = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
}
int exitCode = process.waitFor();
return ResponseEntity.ok("Exit code: " + exitCode + "\n\n" + output.toString());
}
This endpoint launches a Maven test process in a subprocess, runs the test suite, and returns the output.
You can even use Surefire reports to parse structured XML output or hook into JaCoCo for code coverage.
5. 📊 Adding Coverage Reports
To integrate code coverage, just add the following Maven plugin:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.10</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
After your mvn test, the coverage HTML report will be available at:
target/site/jacoco/index.html
Parse it, zip it, or serve it from a frontend.
6. ✅ Summary: What Can a Service Do to Test Itself?
7. Bonus 💡: Using Actuator for Self Health Checks
Although JUnit is not ideal for live runtime tests, Spring Boot’s Actuator module gives you:
These can help create a lightweight runtime self-check without executing test suites.
Combine them with Prometheus/Grafana or external health polling.
🔚 Conclusion
While the idea of a self-testing service is brilliant in theory, in practice it requires a clean separation of concerns. Tests belong to the test lifecycle, and production services should stay lean.
However, with the strategies we explored:
💬 Your Turn!
If you’ve tried something similar, or if you think there’s a better/cleaner approach — 👉 Feel free to share your views or corrections in the comments! I’d love to hear how others handle this challenge across teams and stacks. 🙌
Happy testing! 🧪