javaspringspring-bootmockitojunit5

Unsatisfied Dependency Error: Missing TestService1 Bean in EmployeeController


I'm encountering the following error while running my EmployeeControllerTest class. I've been instructed not to modify the controller class. Could anyone please help me resolve this issue?

enter image description here

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name '....controllers.EmployeeController': Unsatisfied dependency expressed through field 'testService1': No qualifying bean of type '...services.TestService1' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

I have attempted the following approaches:

Using reflection to set private fields, Constructor injection in the test class, Creating a Test Configuration class to add the necessary beans, Setting up dependencies manually, I’m still unable to resolve the issue.

@ContextConfiguration(classes = {TestConfiguration.class})  
@WebFluxTest(controllers = EmployeeController.class)  
@Profile("local")  
public class EmployeeControllerTest {  
@MockBean  
private TestService1 testService1;  
@MockBean  
private TestService2 testService2;  
private WebTestClient webTestClient;  /* If I use @Autowired for this field, I'm getting an UnsatisfiedDependencyException: Error creating bean due to an unsatisfied dependency expressed through the field 'webTestClient'.*/
@InjectMocks  
private EmployeeController employeeController;  
private JwtAuthenticationToken jwtAuthenticationToken;  
int pageIndex=0;  
int pageSize=10;  

@BeforeEach  
void setup(){  
MockitoAnnotations.openMocks(this);  
jwtAuthenticationToken = mock(JwtAuthenticationToken.class);  
when(jwtAuthenticationToken.getName()).thenReturn("testUser");  
/*If I don't use the code below, I get the error: Cannot invoke "org.springframework.test.web.reactive.server.WebTestClient.post()" because "this.webTestClient" is null.*/
webTestClient = WebTestClient.bindToController(employeeController).webFilter(new SecurityContextServerWebExchangeWebFilter()).build();  
}  
@Test  
void testEmployee(){  
Page<EmployeeDto> mockPage = new PageImpl<>(Collections.emptyList());  
when(testService1.list(eq(jwtAuthenticationToken), eq(pageIndex), eq(pageSize))).thenReturn(Mono.just(mockPage));  
webTestClient.post() .uri("/test/employee/{index}/{size}",pageIndex,pageSize).exchange().expectStatus().isOk().expectBodyList(EmployeeDto.class).hasSize(0);  
}}  

/*Controller Code:I've been instructed not to modify the controller class*/  
@RestController  
@RequestMapping("/test")  
public class EmployeeController {  
@Autowired  //Field injection is not recommended   
private TestService1 testService1;  
@Autowired  //Field injection is not recommended   
private TestService2 testService2;  
@PostMapping("/employee/{index}/{size}")  
public Mono<Page<EmployeeDto>> users(@NonNull JwtAuthenticationToken principal, @PathVariable int index, @PathVariable int size) {  
return testService1.list(principal, index, size);  
}  
}  

Solution

  • The test runs successfully, as follows. Note that openMocks is not needed, neither is @ExtendWith(MockitoExtension.class) that I had mentioned in an earlier version.

    1. add @Autowired to the declaration of WebTestClient
    2. (Important) remove initialization assignment of WebTestClient, since it is handled through injection above.
    3. Use MockBean with private EmployeeController employeeController;.

    Complete setup of test below, test cases omitted:

    @ContextConfiguration(classes = {TestConfiguration.class})
    @WebFluxTest(controllers = EmployeeController.class)
    public class EmployeeControllerTest {
    
        @MockBean
        private TestService1 testService1;
    
        @MockBean
        EmployeeController employeeController;
    
        @Autowired
        private WebTestClient webTestClient;
    
        private JwtAuthenticationToken jwtAuthenticationToken;
    
        int pageIndex=0;
        int pageSize=10;
    
        @BeforeEach
        void setup(){
            jwtAuthenticationToken = mock(JwtAuthenticationToken.class);
            when(jwtAuthenticationToken.getName()).thenReturn("testUser");
            //do not use: WebTestClient.bindToController
        }
    
        @Test
        void testEmployee(){
            Page<EmployeeDto> mockPage = new PageImpl<>(Collections.emptyList());
    
            when(testService1.list(eq(jwtAuthenticationToken), eq(pageIndex), eq(pageSize))).thenReturn(mockPage);
    
    
            webTestClient
                    .post()
                    .uri("/test/employee/{index}/{size}",pageIndex,pageSize)
                    .exchange()
                    .expectStatus().isOk().expectBodyList(EmployeeDto.class).hasSize(0);
        }
    
    }
    

    With this solution you cannot use SecurityContextServerWebExchangeWebFilter; it would be tested separately.

    Regarding the pom.xml, with this configuration the above test works, including the assertions, as is:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>3.3.5</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>xa.xa.xa</groupId>
        <artifactId>plain</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>plain</name>
        <description>plain</description>
        <properties>
            <java.version>21</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web-services</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-webflux</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-commons</artifactId>
            </dependency>
    
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>io.projectreactor</groupId>
                <artifactId>reactor-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>