Repository Interface: Same Test For More Than One Implementation

Posted on Posted in programming

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.

Test result
Test result

Conclusion

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

Leave a Reply