I’m currently working on a Java project using Java 17, Dropwizard, and JUnit 5, and I’m focusing on improving my unit tests and adopting Test-Driven Development (TDD) practices. My application interacts with a database through DAO interfaces, and I’m exploring the best ways to test these interactions, especially for methods that perform operations like inserting data into the database but don’t return a value.
Given the nature of these methods, I’m considering two main approaches for testing:
I understand that fake implementations offer speed and simplicity by avoiding the overhead of setting up a real database connection, making them ideal for unit testing. On the other hand, in-memory databases provide a more realistic test environment, which seems beneficial for integration testing to ensure that my SQL queries and transactions behave as expected.
My Questions:
I’m aiming for a testing strategy that not only ensures reliability and maintainability but also aligns with best practices for TDD. Any insights, experiences, or recommendations you could share would be greatly appreciated.
Thank you!
In the context of TDD and considering the balance between speed and realism in testing, which approach would you recommend for testing methods that don’t return a value?
Test behaviour, not methods. As you mentioned, the behaviour you want is that you can write an item successfully and the way you test that behaviour is that you try and read the item. This might look something like
@Test
public void it_can_save_an_item() {
MyDao dao = new MyDao();
Item item = new Item("foo", "bar");
dao.save(item);
List<Item> savedItems = dao.findAll();
assertEquals(item, savedItems.get(0));
}
Personally, I don't use in-memory databases, for the same reasons that others have mentioned above.
The approach I'd tend to take is to use a fake and to integration test the real implementation, using contracts to ensure that both versions of the abstraction behave in the same way. See, for example, https://quii.gitbook.io/learn-go-with-tests/testing-fundamentals/working-without-mocks on fakes and contracts (examples are in Go, but the advice is independent of language really).
My perspective here comes from doing outside-in (or "London style") TDD, where one drives out the design of the system from an acceptance test. That is, you're figuring out what abstractions you need and how they should look and fakes are quite a lightweight way to do help there (but there are other reasons - see the link above).
I suppose if you're doing more inside-out ("Chicago style") and you're unit-testing a component that needs the DAO, you still want the same kind of confidence - that you can test your component with a version of the DAO that behaves like the real thing.