Unit testing Spring Boot application Service layer

Viewed 2303

I'm a bit confused about the test capabilities and utilities provided by Spring Boot.

I'm using spring-boot-starter-test in my project and I'd like to unit test my services without the database connection

At the moment I'm using @WebMvcTest for contoller test suites and @SpringBootTest for all the other test classes.

But I read somewhere that @SpringBootTest is meant to be used only in integration tests...

Reading documentation I didn't understood what's the suggested approach for services. Should I only test them in integration with repos?

UPDATE

That's an excerpt of a test class for one of my services:

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@SpringBootTest
internal class SignupServiceTest(
        @Autowired val signupService: SignupService
) {
    @MockkBean
    lateinit var userRepository: UserRepository

    @Test
    fun `should return exception if username already used`() {
        every { userRepository.findByUsername("registered-user") } returns fakeUser(username = "registered-user")

        assertThatThrownBy {
            signupService.createNewAccount(fakeSignupForm(username = "registered-user"))
        }.isExactlyInstanceOf(UsernameNotAvailableException::class.java)
    }

    // ... other tests

}
4 Answers

You should favor implementing unit tests to test your business logic (tests running without Spring, plain JUnit tests) over integration tests (tests starting a Spring container, @SpringBootTest) as they are more lightweight and give you feedback a lot faster.

Quote from Spring Boot doc

One of the major advantages of dependency injection is that it should make your code easier to unit test. You can instantiate objects by using the new operator without even involving Spring. You can also use mock objects instead of real dependencies.

Often, you need to move beyond unit testing and start integration testing (with a Spring ApplicationContext). It is useful to be able to perform integration testing without requiring deployment of your application or needing to connect to other infrastructure.

Using @SpringBootTest for unit tests is a bit of a overkill. Because this would boot up the whole application context.

To test individual (service) classes I would go with @RunWith(MockitoJUnitRunner.class) and instead of @Autowired and @MockBean use @Mock and @InjectMocks(If you use constructor injection, you wouldn't have to use this. which would be the better option)

You could still use @Autowired with @ContextConfiguration and load specific classes(if there are not too many transitive dependencies)

If you do not want to use mocks, then you can use embedded databases and use @DataMongoTest or @DataJpaTest and use Springboot testing capabilities.

Keep it simple....

try to mock the calls to database using mockito, or use h2 database for the tests

I was previously using @SpringBootTest as well then realized it was also trying to connect to my database, which should not be needed since it's a simple service test and all dependencies are mocked. After a bit of research I found this solution works quite well. Also note now my spring properties were not being injected so I used @Spy to create a pojo property object with some test values.

@ExtendWith(MockitoExtension.class)
public class MyServiceTest {

/**
 * This is the bean under test as it is not a mock
 */
@InjectMocks
private MyService myService;

@Mock
private OtherService otherService;

@Spy
private MyServiceProperties properties = mockProperties();

@BeforeEach
public void configureMocks() {
   given(this.otherService.getData(any())).willReturn(mockDataResponse());
}

@Test
void testMyServiceReport_Success() {
    myService.runReport();

//assert response as needed
    verify(otherService, times(1)).getData(any());
}

Also here is some help with the imports which can sometimes be confusing:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.mockito.BDDMockito.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.times;
import static org.mockito.BDDMockito.verify;
Related