2

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:

  1. Using Fake Implementations: Creating fake implementations of my DAO interfaces to simulate database operations in memory
  2. Using In-Memory Databases: Utilizing an in-memory database like H2 to execute real database operations in a controlled environment.

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:

  1. 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?
  2. Are there specific scenarios or project phases where one approach is clearly preferable over the other?

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!

asked Mar 24, 2024 at 13:19
7
  • 2
    Using an H2 in-memory database makes a "more realistic test environment" only if you will be deploying an H2 in-memory database. Commented Mar 24, 2024 at 17:38
  • 3
    Test against your actual database software with a test container and spin up a companion service as part of your CI (or also use the test container, security policies allowing). testcontainers.com Commented Mar 24, 2024 at 22:25
  • @M.P.Korstanje as I understand, I have to provide create table query for each table I want to use with testcontainers? Right now I don't have them in my code and I'm not ensuring that those tables exists so adding it would be time consuming. How do u handle this problem? Commented Mar 26, 2024 at 22:01
  • @M.P.Korstanje also in their example they send DbConnectionProvider in constructor. I use dropwizard and here common thing is passing DAO to constructor so it would be harder to adapt for using testcontainers Commented Mar 26, 2024 at 22:23
  • I'm not familiar with Dropwizard but it looks like a reasonably flexibele framework and it supports DI. So you can probably find a place replace your DB connection with one provided by a test container. To insert tables a tool like Flyway or Liquibase might work. Then insert the remaining test data as part of the test. If that doesn't work, you may have to refactor your application to make it testable. Commented Mar 26, 2024 at 23:00

2 Answers 2

2

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.

answered Mar 24, 2024 at 18:27
Sign up to request clarification or add additional context in comments.

Comments

1

Do both. Apply the lessons from the Test Pyramid. Most tests should run fast, which often works best when running entirely in memory. On the other hand, there are going to be implementation errors or bugs that such tests can't flush out, so enhance the unit tests with a smaller set of integration tests.

I'd typically recommend that integration tests use the database technology that is also going to be used in production. Thus, unless you also plan to use an in-memory database in production, don't use one for integration testing. If you use Oracle in production, run the integration tests against Oracle, etc.

Most database technologies enable you to fully automate creation, configuration, and teardown of databases, so make sure that you automate the database as part of the standard four-phase test pattern. In other words, don't run integration tests against a shared 'test database', but make sure that each integration test runs against an isolated database that exists solely for that purpose. I usually create the database in the test's 'setup' phase and delete it again in the tests' 'teardown' phase.

xUnit Test Patterns have much useful information about that, and many other things related to unit testing.

answered Mar 24, 2024 at 14:06

1 Comment

Although I agree with your recommendation, the original question was about fake implementations vs in-memory databases, not vs the real database. I think this question is discussed in James Shore's "Testing Without Mocks" pattern language: jamesshore.com/v2/projects/nullables/testing-without-mocks

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.