JUnit - Share Tests Of Different Implementations

Viewed 268

I'm looking for the most readable way to share tests of different implementations of an interface.

The most popular, but rather old question on this subject was - Writing a single unit test for multiple implementations of an interface.

To the question above, 2 main and different answers were given -

  1. Parameterized tests.
  2. Test inheritance.

I'm not satisfied with both answers.

  1. Parameterized test - the answer doesn't include a code example of how to parameterize each subclass. Also, I personally have a hard time with parameterized test and I find the API not intuitive at all.

  2. I always fear inheritance overusing, and I'm not sure if a test inheritance is a good practice.

I wonder what is the best answer to this question in 2018.

2 Answers

Parameterizing your test still seems like the textbook solution to such usecases. JUnit Jupiter's syntax does make it a tad more elegant, though. The API is quite clearer, IMHO (the tests have arguments, and an annotation shows where they come from):

public class ListTest {

    public static Stream<List<String>> lists() {
        return Stream.of(new ArrayList<>(), new LinkedList<>());
    }

    @ParameterizedTest
    @MethodSource("lists")
    public void testAdd(List<String> list) {
        list.add("xyz");
        assertEquals(1, list.size());
        assertFalse(list.isEmpty());
        assertEquals("xyz", list.get(0));
    }
}

Probably not the most Java-like, but you can follow a table-driven test format. Using an local class, to keep test most readable, and keep context as close to real test as possible.

Note: this is very similar to underlying high-level approach of @RunWith(Parameterized.class)

// Assuming Animal interface has a `public boolean canDance()`

@Test
public void TestAnimalCanDance() {
    class Tester {
        String errMsgFmt = "%s failed the test";
        boolean expected;
        Animal animal;
        public Tester(boolean expected, Animal animal) {
            this.expected = expected;
            this.animal = animal;
        }
    }

    Tester dog = new Tester(true, new Dog());
    Tester cat = new Tester(false, new Cat());
    Tester monkey = new Tester(false, new Monkey());
    Tester[] tests = Arrays.asList(dog, cat, monkey);

    for (Tester t: tests) {
        boolean actual = t.canDance();
        assertTrue(actual == t.expected);
    }
}
Related