Skip to content

Ferdinand Agyei-Yeboah

Writing JPA Integration Tests

May 28, 2023

For when you want to write a test that interacts with the database.

How to write a JPA integration test

1. Create a new test class and configure to use Spring JPA

@DataJpaTest // OR @SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) //Tells spring to not try to use an embedded database
@ComponentScan(basePackages = {"org.example"})
@TestPropertySource(locations = "classpath:application-test.properties") //OR @ActiveProfiles("test")
@ExtendWith(MockitoExtension.class) //Junit 5
public class SampleIntegrationTest {
@InjectMocks
@Autowired
ServiceUnderTest service;
@Mock
ExternalAPIService externalAPIService;
@Autowired //Autowire the jpa beans you want to use instead of mocking them
UserRepository userRepo;
@Autowired
EmailRepository emailRepository;

Explanation of setup

  • Required annotations
    • @DataJpaTest or @SpringBootTest: These are responsible for loading the Spring context into your test. @DataJpaTest is a test splice that only loads in the JPA related spring beans while @SpringBootTest loads the entire Spring context, including non jpa related beans. This means @DataJpaTest is lighterweight, and you can use it if you don’t need any non-jpa beans. Can use @ComponentScan in conjuction with DataJpaTest if there are certain non-jpa beans you want to load.
    • @ExtendWith(MockitoExtension.class): Junit 5 Mockito extension that allows you to use Mockito features within your Junit test. Features like annotation based mocks, mock injection, etc.
  • Optional annotations
    • @AutoConfigureTestDatabase: Controls Spring JPA test database behavior. By default Spring tries to create an embedded database for your tests. @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) tells Spring to not use an embedded database.
    • @ComponentScan: Since @DataJpaTest only brings in jpa related beans, we use @ComponentScan to bring in our custom beans in the org.example package.
    • @TestPropertySource or @ActiveProfiles: This lets us control properties files for our test. @TestPropertySource allows you to specify the properties or configuration files to be loaded during test execution. @ActiveProfiles lets us choose a spring profile to use and thus loads corresponding properties file (which must be under src/main/resources).

You can then see the class to be tested is annotated with @InjectMocks and @Autowired, any fully mocked services are annotated with @Mock and the JPA related beans (which we do not want to mock) are annotated with @Autowired to pull the actual bean from the Spring Context (provided by @DataJpaTest).

2. Write test & committing database changes in test

Let’s pretend we want to do a test which adds a user and then checks in the database if the user was added. Let’s assume the service reaches out to an external api (which we plan to mock) to get the user email and then uses the user repository to insert the user in the database. The test would look something like this.

@Test
//@Commit - Only if want database changes to be persisted
public void addNewUserTest(){
//Mock
when(externalAPIService.getUserEmail()).thenReturn("abc@example.com");
//Execute
service.addNewUser();
//Verify - could also do something like userRepo.findByUserEmail("abc@example.com")
assertEquals(1, userRepo.count());
}

Note: Default behavior is rollback database changes after each test, this is preferred for testing but if you want the database changes to persist after each test then use @Commit.

Common Issues

Issue: Using @DataJpaTest but code needs some beans that are not jpa related, example your own spring config file.
Fix: @ComponentScan(basePackages = {“your.package”}) to include your spring beans OR use @SpringBootTest
Example of issue

***************************
APPLICATION FAILED TO START
***************************
Description:
Field objectMapper in org.example.Main required a bean of type 'com.fasterxml.jackson.databind.ObjectMapper' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.fasterxml.jackson.databind.ObjectMapper' in your configuration.

Issue: Using @DataJpaTest for tests that are not in same package as source code (and thus application context). Ex: tests in org.randompkg, code in org.example.app
Fix: Create test code in same package structure as source code, OR possibly try using @ContextConfiguration to specify application context. Recommend first approach.
Example of issue

Test ignored.
java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test

Issue: Using @DataJpaTest without having an embedded database on classpath
Fix: If want to use an embedded database, add embedded database test dependency. If want to use real database, add @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) to tell Spring to use real database
Example of issue

Caused by: java.lang.IllegalStateException: Failed to replace DataSource with an embedded database for tests. If you want an embedded database please put a supported one on the classpath or tune the replace attribute of @AutoConfigureTestDatabase.

Software Engineering Tutorials & Best Practices