Reputation: 185
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
Upvotes: 7
Views: 4196
Reputation: 11
I tried lotor but for me it doesn't work from start and I found 2 ways to make it works.
jupiter : 5.9.2 (Jan 10, 2023) https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api
1 - Declare Implements Classes inside Test Interface. Code example:
public interface StringDiagnoseTest<T extends StringDiagnose> {
T createDiagnose();
@Test
default void blankCheckFollowsContract() {
assertTrue(createDiagnose().isTheStringBlank("\t\n "));
assertFalse(createDiagnose().isTheStringBlank("\t\n ! \r\n"));
}
class DefaultDiagnoseTest implements StringDiagnoseTest<DefaultDiagnose> {
@Override
public DefaultDiagnose createDiagnose() {
return new DefaultDiagnose();
}
}
class StreamBasedDiagnoseTest implements StringDiagnoseTest<StreamBasedDiagnose> {
@Override
public StreamBasedDiagnose createDiagnose() {
return new StreamBasedDiagnose();
}
}
}
2 - If you use Intelligent IDEA, check the build/run configuration, it should looks like that:
For property "The type of resource to search for tests" select pattern and value:
my_test_package.StringDiagnoseTest$DefaultDiagnoseTest||
my_test_package.StringDiagnoseTest$StreamBasedDiagnoseTest
If you have problem, probably(by default) selected class and value StringDiagnoseTest
.
Upvotes: 1
Reputation: 185
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
);
}
Upvotes: 1
Reputation: 1090
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.
Upvotes: 11