Should I write Unit-Tests for CRUD operations when I have already Integration-Tests?

fischermatte picture fischermatte · Sep 18, 2012 · Viewed 7.8k times · Source

In our recent project Sonar was complaining about a weak test coverage. We noticed that it didn't consider integration tests by default. Beside the fact that you can configure Sonar, so it will consider them (JaCoCo plugin), we were discussing the question in our team if there really is the need to write Unit Tests, when you cover all your service and database layer with integration tests anyway.

What I mean with integration tests is, that all our tests run against a dedicated Oracle instance of the same type we use in production. We don't mock anything. If a service depends on another service, we use the real service. Data we need before running a test, we construct through some factory classes that use our Services/Repositories (DAOs).

So from my point of view - writing integration tests for simple CRUD operations especially when using frameworks like Spring Data/Hibernate is not a big effort. It is sometimes even easier, because you don't think of what and how to mock.

So why should I write Unit Tests for my CRUD operations that are less reliable as the Integration Tests I can write?

The only point I see is that integration tests will take more time to run, the bigger the project gets. So you don't want to run them all before check-in. But I am not so sure if this is so bad, if you have a CI environment with Jenkins/Hudson that will do the job.

So - any opinions or suggestions are highly appreciated!

Answer

Ray Toal picture Ray Toal · Sep 18, 2012

If most of your services simply pass through to your daos, and your daos do little but invoke methods on Spring's HibernateTemplate or JdbcTemplate then you are correct that unit tests don't really prove anything that your integration tests already prove. However, having unit tests in place are valuable for all the usual reasons.

Since unit tests only test single classes, run in memory with no disk or network access, and never really test multiple classes working together, they normally go like this:

  • Service unit tests mock the daos.
  • Dao unit tests mock the database driver (or spring template) or use an embedded database (super easy in Spring 3).

To unit test the service that just passes through to the dao, you can mock like so:

@Before
public void setUp() {
    service = new EventServiceImpl();
    dao = mock(EventDao.class);
    service.EventDao = dao;
}

@Test
public void creationDelegatesToDao() {
    service.createEvent(sampleEvent);
    verify(dao).createEvent(sampleEvent);
}

@Test(expected=EventExistsException.class)
public void creationPropagatesExistExceptions() {
    doThrow(new EventExistsException()).when(dao).createEvent(sampleEvent);
    service.createEvent(sampleEvent);
}

@Test
public void updatesDelegateToDao() {
    service.updateEvent(sampleEvent);
    verify(dao).updateEvent(sampleEvent);
}

@Test
public void findingDelgatesToDao() {
    when(dao.findEventById(7)).thenReturn(sampleEvent);
    assertThat(service.findEventById(7), equalTo(sampleEvent));

    service.findEvents("Alice", 1, 5);
    verify(dao).findEventsByName("Alice", 1, 5);

    service.findEvents(null, 10, 50);
    verify(dao).findAllEvents(10, 50);
}

@Test
public void deletionDelegatesToDao() {
    service.deleteEvent(sampleEvent);
    verify(dao).deleteEvent(sampleEvent);
}

But is this really a good idea? These Mockito assertions are asserting that a dao method got called, not that it did what was expected! You will get your coverage numbers but you are more or less binding your tests to an implementation of the dao. Ouch.

Now this example assumed the service had no real business logic. Normally the services will have business logic in addtion to dao calls, and you surely must test those.

Now, for unit testing daos, I like to use an embedded database.

private EmbeddedDatabase database;
private EventDaoJdbcImpl eventDao = new EventDaoJdbcImpl();

@Before
public void setUp() {
    database = new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("schema.sql")
            .addScript("init.sql")
            .build();
    eventDao.jdbcTemplate = new JdbcTemplate(database);
}

@Test
public void creatingIncrementsSize() {
    Event e = new Event(9, "Company Softball Game");

    int initialCount = eventDao.findNumberOfEvents();
    eventDao.createEvent(e);
    assertThat(eventDao.findNumberOfEvents(), is(initialCount + 1));
}

@Test
public void deletingDecrementsSize() {
    Event e = new Event(1, "Poker Night");

    int initialCount = eventDao.findNumberOfEvents();
    eventDao.deleteEvent(e);
    assertThat(eventDao.findNumberOfEvents(), is(initialCount - 1));
}

@Test
public void createdEventCanBeFound() {
    eventDao.createEvent(new Event(9, "Company Softball Game"));
    Event e = eventDao.findEventById(9);
    assertThat(e.getId(), is(9));
    assertThat(e.getName(), is("Company Softball Game"));
}

@Test
public void updatesToCreatedEventCanBeRead() {
    eventDao.createEvent(new Event(9, "Company Softball Game"));
    Event e = eventDao.findEventById(9);
    e.setName("Cricket Game");
    eventDao.updateEvent(e);
    e = eventDao.findEventById(9);
    assertThat(e.getId(), is(9));
    assertThat(e.getName(), is("Cricket Game"));
}

@Test(expected=EventExistsException.class)
public void creatingDuplicateEventThrowsException() {
    eventDao.createEvent(new Event(1, "Id1WasAlreadyUsed"));
}

@Test(expected=NoSuchEventException.class)
public void updatingNonExistentEventThrowsException() {
    eventDao.updateEvent(new Event(1000, "Unknown"));
}

@Test(expected=NoSuchEventException.class)
public void deletingNonExistentEventThrowsException() {
    eventDao.deleteEvent(new Event(1000, "Unknown"));
}

@Test(expected=NoSuchEventException.class)
public void findingNonExistentEventThrowsException() {
    eventDao.findEventById(1000);
}

@Test
public void countOfInitialDataSetIsAsExpected() {
    assertThat(eventDao.findNumberOfEvents(), is(8));
}

I still call this a unit test even though most people might call it an integration test. The embedded database resides in memory, and it is brought up and taken down when the tests run. But this relies on the fact that the embedded database looks the same as the production database. Will that be the case? If not, then all that work was pretty useless. If so, then, as you say, these tests are doing anything different than the integration tests. But I can run them on demand with mvn test and I have the confidence to refactor.

Therefor, I write these unit tests anyway and meet my coverage targets. When I write integration tests, I assert that an HTTP request returns the expected HTTP response. Yeah it subsumes the unit tests, but hey, when you practice TDD you have those unit tests written before your actual dao implementation anyway.

If you write unit tests after your dao, then of course they are no fun to write. The TDD literature is full of warnings about how writing tests after your code feels like make work and no one wants to do it.

TL;DR: Your integration tests will subsume your unit tests and in that sense the unit tests are not adding real testing value. However when you have a high-coverage unit test suite you have the confidence to refactor. But of course if the dao is trivially calling Spring's data access template, then you might not be refactoring. But you never know. And finally, though, if the unit tests are written first in TDD style, you are going to have them anyway.