May I ask how to write a junit 5 test for an interface with different implementations?
For example, I have a interface Solution
, with different implementations like SolutionI
, SolutionII
, can I write only one test class to test both?
There is a post shows an example, but if there are multiple test method that needs to be called, I have to pass the parameter for every test method.
May I ask if there is an elegant way like what is used in the Junit4
In Junit4, I have a very elegant code sample as follows
@RunWith(Parameterized.class)
public class SolutionTest {
private Solution solution;
public SolutionTest(Solution solution) {
this.solution = solution;
}
@Parameterized.Parameters
public static Collection<Object[]> getParameters() {
return Arrays.asList(new Object[][]{
{new SolutionI()},
{new SolutionII()}
});
}
// normal test methods
@Test
public void testMethod1() {
}
}
Junit 5 claims ExtendWith()
is similar, I tried the following code
@ExtendWith(SolutionTest.SolutionProvider.class)
public class SolutionTest {
private Solution solution;
public SolutionTest(Solution solution) {
System.out.println("Call constructor");
this.solution = solution;
}
@Test
public void testOnlineCase1() {
assertEquals(19, solution.testMethod(10));
}
@Test
public void testOnlineCase2() {
assertEquals(118, solution.testMethod(100));
}
static class SolutionProvider implements ParameterResolver {
private final Solution[] solutions = {
new SolutionI(),
new SolutionII()
};
private static int i = 0;
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return parameterContext.getParameter().getType() == Solution.class;
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
System.out.println(i);
return solutions[i++];
}
}
}
Unfortunately, testMethod1
is using SolutionI
and testMethod2
is using SolutionII
, which makes sense, I don't know if this helps to inspire a little bit.
Thanks for the help in advance
Jupiter provides Test interfaces exactly for your purpose - to test interface contract .
For example, let's have an interface for string diagnostic contract and two implementations following the contract but exploiting different implementation ideas:
public interface StringDiagnose {
/**
* Contract: a string is blank iff it consists of whitespace chars only
* */
boolean isTheStringBlank(String string);
}
public class DefaultDiagnose implements StringDiagnose {
@Override
public boolean isTheStringBlank(String string) {
return string.trim().length() == 0;
}
}
public class StreamBasedDiagnose implements StringDiagnose {
@Override
public boolean isTheStringBlank(String string) {
return string.chars().allMatch(Character::isWhitespace);
}
}
According to the recommended approach you are to create test interface that verifies the diagnostic contract in default
methods and exposes implementation-dependent pieces to hooks:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
public interface StringDiagnoseTest<T extends StringDiagnose> {
T createDiagnose();
@Test
default void blankCheckFollowsContract(){
assertTrue(createDiagnose().isTheStringBlank("\t\n "));
assertFalse(createDiagnose().isTheStringBlank("\t\n ! \r\n"));
}
}
and then implement this test interface for each solution specific:
class DefaultDiagnoseTest implements StringDiagnoseTest<DefaultDiagnose> {
@Override
public DefaultDiagnose createDiagnose() {
return new DefaultDiagnose();
}
}
class StreamBasedDiagnoseTest implements StringDiagnoseTest<StreamBasedDiagnose> {
@Override
public StreamBasedDiagnose createDiagnose() {
return new StreamBasedDiagnose();
}
}
Use more hooks and not- default
interface methods to test same-named solutions' aspects (like performance) and define new tests in the interface implementations for completely distinctive implementation pecularities.
Sorry for not replying to this thread for a while. Comparing to the lotor's answer, I found some other ways I am currently adopting:
@ParameterizedTest
@MethodSource("solutionStream")
void testCase(Solution solution) {
// add your test
}
static Stream<Solution> solutionStream() {
return Stream.of(
new SolutionI(),
new SolutionII()
);
}
The constructor needs parameters (Not type-safe)
@ParameterizedTest
@MethodSource("solutionStream")
void testOnlineCase(Class<Solution> solutionClass) throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException {
Solution solution = solutionClass.getConstructor(Integer.TYPE).newInstance(2);
}
static Stream<Class> solutionStream() {
return Stream.of(
SolutionI.class
);
}
For example, I have a interface Solution, with different implementations like SolutionI, SolutionII, can I write only one test class to test both?
The short answer is that you shouldn't do that . As it's best practice that for UT each implementation would have its own test class so that if one implementation changes, then only relevant tests will be impacted. Please find below some additional thoughts:
If you have two implementations of the same interface, I guess the logic is different otherwise why bothering having two implementations in the first place? So you should have two set of tests;
If you have common logic between the two implementations, then you should put it in an abstract class that would be extended by your implementations.
ParameterizedTest shouldn't be abused to deviate from the best pattern;
In other to avoid code replication for tests, according to your use case, In JUnit5 You can indeed use extensions as explained here in the documentation.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.