We have an entity and a repository interface for the entity. The repository implementation has often access to external resources like databases, files, etc. When we have the repository, we also have tests for it. What if we wanted implement another repository? Can we use the same tests?
Example in Java
The complete code is on GitHub.
The entity Foo:
public class Foo implements Serializable { @Id @Column private String id; @Column private String bar; }
The repository interface:
public interface FooRepository { Foo findById(String id); void save(Foo foo); }
Test for repository:
@Test public void simple_test() throws Exception { getFooRepository().save(new Foo("1", "bar1")); assertThat(getFooRepository().findById("1")).isNotNull(); assertThat(getFooRepository().findById("1").getBar()).isEqualTo("bar1"); }
Method getFooRepository gets repository instance. Let’s look implementations for repository.
At first, we will implement the repository so that it has access to database. We will use Spring Boot.
@Repository public class FooRepositoryDatabase implements FooRepository { @PersistenceContext EntityManager entityManager; @Override public Foo findById(String id) { return entityManager .createQuery("SELECT f FROM Foo f WHERE f.id = :id", Foo.class) .setParameter("id",id) .getSingleResult(); } @Override public void save(Foo foo) { entityManager.persist(foo); } }
And Spring configuration:
@SpringBootApplication @EnableTransactionManagement(proxyTargetClass = true) @ComponentScan @EntityScan public class Application { }
Complete test for Spring version:
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(Application.class) @Transactional public class FooRepositoryTest_Spring { @Autowired protected FooRepository fooRepositoryDatabase; public FooRepository getFooRepository() { return fooRepositoryDatabase; } @Test public void simple_test() throws Exception { getFooRepository().save(new Foo("1", "bar1")); assertThat(getFooRepository().findById("1")).isNotNull(); assertThat(getFooRepository().findById("1").getBar()).isEqualTo("bar1"); } }
Now we will crate a new implementation which uses class HashMap.
public class FooRepositoryMap implements FooRepository { private Map<String, Foo> map = new HashMap<>(); @Override public Foo findById(String id) { return map.get(id); } @Override public void save(Foo foo) { map.put(foo.getId(),foo); } }
Let’s create upgrade the test class so that the test method remains the same.
@RunWith(Parameterized.class) @SpringApplicationConfiguration(Application.class) @Transactional public class FooRepositoryTest { @ClassRule public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule(); @Rule public final SpringMethodRule springMethodRule = new SpringMethodRule(); @Autowired protected FooRepository fooRepositoryDatabase; protected FooRepository fooRepositoryMap; @Parameterized.Parameter(0) public String parameter0; private static final String DATABASE = "database"; private static final String MAP = "map"; @Parameterized.Parameters(name = "{0}") public static String[] parameter() { return new String[]{DATABASE, MAP}; } public FooRepository getFooRepository() { if (parameter0.equals(DATABASE)) { return fooRepositoryDatabase; } else if (parameter0.equals(MAP)) { if (fooRepositoryMap == null) { fooRepositoryMap = new FooRepositoryMap(); } return fooRepositoryMap; } throw new RuntimeException("Invalid parameter."); } @Test public void simple_test() throws Exception { getFooRepository().save(new Foo("1", "bar1")); assertThat(getFooRepository().findById("1")).isNotNull(); assertThat(getFooRepository().findById("1").getBar()).isEqualTo("bar1"); } }
We use JUnit runner @RunWith(Parameterized.class), which can run same tests with different input values. Tests will be run two times, because method parameter returns array of two values. For each time method getFooRepository returns different implementation for FooRepository.
JUnit can have only one runner. Spring context is loaded with @ClassRule and @Rule.

Conclusion
We can use the same tests. We write test for interface and with one test we test two implementations.