-1

I'm working on a REST API using Spring Boot. Currently, the V1 of the API is complete. So, I'm implementing Spring Security to manage authentication and authorization.
Since I've implemented Spring Security, my JUnit Jupiter tests does not work (no one works).

I searched a lot a solution on internet, but all answers I found are for JUnit4 and not JUnit5 (so I don't have all required classes).
I got the classical "Fail to load ApplicationContext" error, but I don't know how to solve it.

Can you help me?

Here is my code for one class (UserController):

gradle.build:

plugins {
    id 'jacoco'
    id 'org.springframework.boot' version '2.6.0'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web:2.5.6'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.5.6'
    implementation 'org.projectlombok:lombok:1.18.22'
    annotationProcessor 'org.projectlombok:lombok:1.18.22'
    developmentOnly 'org.springframework.boot:spring-boot-devtools:2.5.6'
    testImplementation 'org.springframework.boot:spring-boot-starter-test:2.5.6'
    implementation 'com.h2database:h2'
    runtimeOnly 'com.h2database:h2'
}

test {
    systemProperty 'spring.profiles.active', 'test'
    useJUnitPlatform()
    finalizedBy jacocoTestReport
}

Application:

@SpringBootApplication
public class BackendApplication {
    public static void main(String[] args) {
        SpringApplication.run(BackendApplication.class, args);
    }
}

UserController sample:

@RestController
@RequestMapping("/api/v1/users")
public class UserController extends AbstractCrudController<User, Long> {

    @Autowired
    public UserController(CrudService<User, Long> service) { super(service); }

    @GetMapping("")
    @Override
    @Secured({ "ROLE_XXXXX" })
    public ResponseEntity<ResponseListDto<User, Long>> findAll() {
        return super.findAll();
    }

    // ...
}

MockedUserControllerTest sample:

@SpringBootTest
public class MockedUserControllerTest {

    @Mock
    private UserService service;

    @InjectMocks
    private UserController controller;

    private static User user;
    private static List<User> users;

    @BeforeAll
    public static void beforeAll() {
        user = new User();
        user.setId(1L);
        user.setUsername("A user name");
        user.setFirstname("First-Name");
        user.setLastname("Last-Name");
        user.setPassword("A Gre4t P4ssw0rd!");
        user.setMail("first-name.last-name@mail.com");
        user.setBirthDate(Date.valueOf("1980-01-15"));
        user.setKey("A-key");
        user.setNewsletter(Boolean.TRUE);

        users = List.of(user);
    }

    @Test
    public void testFindAll() {
        when(service.findAll()).thenReturn(users);
        assertEquals(new ResponseEntity<>(new ResponseListDto<>(users, null, null), HttpStatus.OK),
                controller.findAll());
    }

    //...
}

Thank you in advance for looking my problem.

Macdrien
  • 34
  • 2
  • 1
    Please include the entire stack trace of a failing test that reports `Fail to load ApplicationContext`. – rieckpil Jan 06 '22 at 11:27
  • 1
    For starters stop mixing Spring Boot versions. Your plugin is 2.6 while your starters are 2.5. Never mix versions of a framework. Finally your test simply doesn't make sense, do you want to use Spring Boot for testing or Mockito? As you now have a test that attempts to use Mockito with Sring Boot (sort of) which won't work. – M. Deinum Jan 06 '22 at 12:54
  • Thank you for your help @rieckpil. As you can see on the bottom, I found the solution :-) – Macdrien Jan 07 '22 at 08:19
  • Thank you @M.Deinum for your comments. I didn't see I had different Spring Boot versions. I'll fix that in my next commit. – Macdrien Jan 07 '22 at 08:20

2 Answers2

1

For a @SpringBootTest you should use @MockBean annotation, because the Spring context will load in order to run the tests. The loaded context will create mocked beans from the dependencies being annotated by @MockBean and it will inject them into that service, which is being tested.

For pure unit tests the @SpringBootTest annotation should be skipped and Mockito (@Mock annotation) can be used. Spring context will not load in this case, the test will focus on that specific class you are testing. With the created Mocks, you can control the behaviour of dependencies, you can arrange different scenarios for your test.

dur
  • 15,689
  • 25
  • 79
  • 125
Isakots
  • 94
  • 3
  • by "dependencies" i mean the other beans, which are injected into the service which is being tested – Isakots Jan 06 '22 at 18:19
  • Thank you for your comment. It helped me to find the solution (https://stackoverflow.com/a/70618339/13523752) – Macdrien Jan 07 '22 at 08:13
0

After some other basic researches (how to write tests with junit5 and mockito), I solved my problem myself.

Here is the answer which helped me: https://stackoverflow.com/a/40962941/13523752

What I wanted is a class test only for the controller I specified. So I didn't need the ApplicationContext. That oriented my research.
Note: I'll do other test classes to test all the process. In this tests I'll need the ApplicationContext.

On my test class, I removed the annotation @SpringBootTest to replace it by @ExtendWith(MockitoExtension.class).

The next thing I did is in the @BeforeAll method I have. I had MockitoAnnotations.initMocks(MockedUserControllerTests.class) to load the mocks I annotated.

Now my test work. I only have to extend this solution on all other mocked test classes.

A sample of the test class I have now:

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class MockedUserControllerTest {
    @Mock
    UserService service;

    @InjectMocks
    UserController controller;

    // ...

    @BeforeAll
    public static void beforeAll() {
        MockitoAnnotations.initMocks(MockedUserControllerTest.class);

        // ...
    }

    // ...
}
Macdrien
  • 34
  • 2
  • You can remove `MockitoAnnotations.initMocks(MockedUserControllerTest.class);`, the `MockitoExtension` takes care to initialize your mocks and inject them to your class under test (`UserController`). – rieckpil Jan 07 '22 at 14:00