5

I'm building a basic HTTP API and some actions like POST /users create a new user record in the database.

I understand that I could mock these calls, but at some level I'm wondering if it's easier to let my Junit tests run against a real (test) database? Is this a bad practice? Should only integration tests run against a real DB?

I'm using flyway to maintain my test schema and maven for my build, so I can have it recreate the test DB with the proper schema on each build. But I'm also worried that I'd need some additional overhead to maintain/clean the state of the database between each test, and I'm not sure if there's a good way to do that.

user2490003
  • 10,706
  • 17
  • 79
  • 155
  • unit tests should test your units (your code) in a controlled environment. so it's better to use mocks than to use a real DB – Stultuske Aug 23 '18 at 06:35
  • 2
    Strictly speaking it is no longer a unit test if you go to an external DB. This does not mean that it isn't useful in some (rare) situations. – Henry Aug 23 '18 at 06:36
  • 2
    why don't you want to use in-memory (i.e. `H2`)? – dehasi Aug 23 '18 at 06:40
  • 1
    You unit test can not depend on external systems because your test would fail if there is a problem with your database even though your code might be working correctly. – Fran Montero Aug 23 '18 at 06:43

5 Answers5

7

Unit tests are used to test single unit of code. This means that you write a unit test by writing something that tests a method only. If there are external dependencies then you mock them instead of actually calling and using those dependencies.

So, if you write code and it interacts with the real database, then it is not a unit test. Say, for some reason your call to db fails then unit test will also fail. Success or failure of your unit test should not be dependent on the external dependencies like db in your case. You have to assume that db call is successful and then hard code the data using some mocking framework(Mockito) and then test your method using that data.

Yug Singh
  • 3,112
  • 5
  • 27
  • 52
  • Thanks! Question about `"if you write code and it interacts with the real database,"` - does this imply it's still reasonable to write code that interacts with a fake (in memory) database like H2? I see a few others in this recommending it. The reason I ask is that it seems like interacting with *any* database makes it not a unit test. Is that just splitting hairs about a unit test definition? Or is it common to find unit tests that run against an in memory DB? – user2490003 Aug 23 '18 at 15:49
  • 1
    IMHO, it is not actually required when you can mock the data instead of using in-memory db. It will be overhead as unit tests should be concerned with testing the functionality only and not concerned with the external dependencies. You may use in-memory db but I don't see it adding any real value over the mocking framework which is easy to use. – Yug Singh Aug 24 '18 at 03:59
3

As several other answers suggest you should create unit tests for testing small pieces of code with mocking all external dependencies.

However sometimes ( a lot of times) it should worth to test whole features. Especially when you use some kind of framework like Spring. Or you use a lot of annotations. When your classes or methods have annotations on them the effects of those annotations usually cannot be tested via unit-tests. You need the whole framework running during the test to make sure it works as expected.

In our current project we have almost as much integration tests as unit tests. We use the H2 in-memory DB for these tests, this way we can avoid failures because of connectivity problems, and Spring's test package could collect and run multiple integration tests into the same test-context, so it has to build the context only once for multiple tests and this way running these tests are not too expensive.

Also you can create separate test context for different part of the project (with different settings and DB content), so this way the tests running under different context won't interfere with each-other.

Do not afraid of using a lot of integration tests. You need some anyway, and if you already have a test-context it's not a big deal adding some more tests into the same context.

Also there are a lot of cases which would take a LOT of effort to cover with unit-tests (or cannot be covered fully at all) but can be covered simply by an integration tests.

A personal experience: Our numerous integration tests were extremely useful when we switched from Spring Boot to Spring Boot 2.

Back to the original question: Unit tests should not connect to real DB, but feel free to use more integration tests. (with in-memory DB)

Selindek
  • 3,269
  • 1
  • 18
  • 25
2

As often, it depends.

On big projects with lots of JUnit tests, the overhead for the performance can be a point. Also the work time needed for the setup of the test data within the database as well as the needed concept for your tests not interfering with the test data of other tests while parallel execution of JUnit tests is a very big argument for only testing against a database if needed and otherwise mock it away.

On small projects this problems may be easier to handle so that you can always use a database but I personally wouldn't do that even on small projects.

Niklas P
  • 3,427
  • 2
  • 15
  • 19
2

Modern development practices recommend that every developer runs the full suite of unit tests often. Unit tests should be reliable (should not fail if the code is OK) Using an external database can interfere with those desiradata.

  • If the database is shared, simultaneous runs of the testsuite by different developers could interfere with each other.
  • Setting up and tearing down the database for each test is typically expensive, and thus can make the tests too slow for frequent execution.

However, using a real database for integration tests is OK. If you use an in-memory database instead of a fully real database, even set up and tear down of the database for each integration test can be acceptably fast.

Raedwald
  • 46,613
  • 43
  • 151
  • 237
1

A popular choice is the use of an in-memory database to run tests. This makes it easy to test, for example, repository methods and business logic involving database calls.

When opting for a "real" database, make sure that every developer has his/her own test database to avoid conflicts. The advantage of using a real database is that this prevents possible issues that could arise because of slight differences in behavior between in-memory and real database. However, test execution performance can be an issue when running a large test suite against a real database.

Some databases can be embedded in a way that the database doesn't even need to be installed for test execution. For example, there is an SO thread about firing up an embedded Postgres in Spring Boot tests.

Markus Pscheidt
  • 6,853
  • 5
  • 55
  • 76