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("[email protected]");
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.
CodePudding user response:
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.
CodePudding user response:
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);
// ...
}
// ...
}
