Classpath Replacer is essential for writing unit tests that require different classpath.
If you need to test different classpath scenarios, this library is must-had.
If you are a library developer, then you may need to test the behavior of your library under different classpath.
For example, you just write a JsonUtil, you are only
responsible for providing the interface, and the specific implementation is up to the user to choose, such as
using Jackson or Gson.
Then you need to test your library in different scenarios:
- With
Jackson, withoutGson - With
Gson, withoutJackson - With both
JacksonandGson - Different versions of behaviors (e.g.
Gson2.8.9 and 2.9.0)
But it is challenging to simulate this scenario in unit tests, because usually, unit tests are run under a fixed classpath.
Classpath Replacer can help you simulate this scenario in your unit tests.
// Gradle
testImplementation "io.github.danielliu1123:classpath-replacer:<latest>"<!-- Maven -->
<dependency>
<groupId>io.github.danielliu1123</groupId>
<artifactId>classpath-replacer</artifactId>
<version>latest</version>
<scope>test</scope>
</dependency>@Classpath is the core annotation of this framework. It can be used on the test class or test method, method-level annotation will override the class-level annotation.
Examples:
// add spring-boot:3.0.0 and its transitive dependencies to the classpath.
@Classpath(add = "org.springframework.boot:spring-boot:3.0.0")
// Exclude spring-boot-3.0.0.jar from the classpath, but not include its transitive dependencies.
// value uses the jar package name.
@Classpath(exclude = "spring-boot-3.0.0.jar")
// Exclude spring-boot-3.0.0.jar from the classpath, but not include its transitive dependencies.
// value uses the maven coordinate.
@Classpath(exclude = "org.springframework.boot:spring-boot:3.0.0")
// Exclude all versions of spring-boot jars in the classpath.
// Using jar package name can't exclude transitive dependencies.
@Classpath(exclude = "spring-boot-*.jar")
// If you want to exclude all versions of spring-boot jars, just omit the version
@Classpath(exclude = "org.springframework.boot:spring-boot")
// Using maven coordinate doesn't exclude the transitive dependencies by default, you can set `excludeTransitive` to true.
@Classpath(exclude = "org.springframework.boot:spring-boot:3.0.0", excludeTransitive = true)
// exclude all versions of spring-boot jars and their transitive dependencies
@Classpath(exclude = "org.springframework.boot:spring-boot", excludeTransitive = true)For the test scenarios of the above JsonUtil, you can write the following tests:
class JsonUtilTest {
@Test
void testNoJsonImplementationOnClasspath() {
assertThrows(ExceptionInInitializerError.class, JsonUtil::instance);
}
@Test
@Classpath(add = "com.google.code.gson:gson:2.10.1")
void testGsonOnClasspath() {
assertTrue(JsonUtil.instance() instanceof Gson);
assertEquals("{}", JsonUtil.toJson(new Object()));
}
@Test
@Classpath(add = "com.fasterxml.jackson.core:jackson-databind:2.14.1")
void testJacksonOnClasspath() {
assertTrue(JsonUtil.instance() instanceof Jackson);
}
@Test
@Classpath(add = {"com.fasterxml.jackson.core:jackson-databind:2.14.1", "com.google.code.gson:gson:2.10.1"})
void useJacksonFirst_whenBothJacksonAndGsonOnClasspath() {
assertTrue(JsonUtil.instance() instanceof Jackson);
}
}The static field will not be reusable!
public class StaticMethodTests {
static AtomicInteger counter = new AtomicInteger(0);
@Test
@Classpath
void test1() {
assertEquals(0, counter.getAndIncrement()); // pass
}
@Test
@Classpath
void test2() {
assertEquals(0, counter.getAndIncrement()); // pass
}
}Because each test method has a different classpath, it causes the test class to be reloaded, and static field/blocks will also be reinitialized.
If you want to use @Classpath with @SpringBootTest, you need to consider the side effects that may come with restarting the Spring context.
If you want to use @Classpath with Testcontainers, you need to consider the side effects that may come with restarting the container.
This project is inspired
by spring-boot-test-support.
However, this library is only for internal use in Spring Boot and does not provide Maven coordinates for external use,
so Classpath Replacer was born.
The MIT License.