Detailed Description

When attempting to write an integration test using the standard Spring Boot testing stack (spring-boot-starter-test), the @SpyBean annotation (from org.springframework.boot.test.mock.mockito) is reported as "cannot resolve symbol" or results in a compilation failure, indicating that the class definition for @SpyBean is either missing or inaccessible, despite the correct dependencies being present.

The test class is correctly configured for JUnit 5 (Jupiter) (no @RunWith annotation is used). This issue suggests a packaging or dependency resolution problem specifically within the spring-boot-test-autoconfigure module in Spring Boot 4.0.0 GA, or a potential conflict with the new modular structure.

This failure prevents standard mocking and spying in Spring Boot 4 integration tests.

Environment Details (Assumed)

The issue is reproducible with the following environment configuration:

  • Spring Boot Version: 4.0.0 (GA)
  • Spring Framework Version: 7.0.1 (GA)
  • Java Version: OpenJDK 21
  • Build Tool: Apache Maven 3.9.9
  • Operating System: Windows 10

Steps to Reproduce

  • Create a standard Spring Boot 4 Maven project structure.
  • Use the pom.xml provided below.
  • Add the ScheduledService.javacomponent (provided below).
  • Add the ScheduledServiceIntegrationTest.java test file (provided below).
  • Attempt to compile or run the test from the terminal.

Command Line Steps

  1. Execute a clean build and package: mvn clean install

  2. Execute the test: mvn test

Expected Result

The test should compile successfully, and mvn test should run the test, leading to a passing result.

Actual Result

Compilation fails with an error similar to:

[ERROR] COMPILATION ERROR : 
[ERROR] /path/to/ScheduledServiceIntegrationTest.java:[8,46] cannot find symbol
  symbol:   class SpyBean
  location: package org.springframework.boot.test.mock.mockito

Problematic Code

The following code is confirmed to be in use and exhibits the issue:

  1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="[http://maven.apache.org/POM/4.0.0](http://maven.apache.org/POM/4.0.0)"
    xmlns:xsi="[http://www.w3.org/2001/XMLSchema-instance](http://www.w3.org/2001/XMLSchema-instance)"
    xsi:schemaLocation="[http://maven.apache.org/POM/4.0.0](http://maven.apache.org/POM/4.0.0) [http://maven.apache.org/xsd/maven-4.0.0.xsd](http://maven.apache.org/xsd/maven-4.0.0.xsd)">

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.0.0</version>
        <relativePath/>
    </parent>

    <!-- ... project coordinates ... -->

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- ... other dependencies ... -->
    </dependencies>

    <!-- ... build section ... -->
</project>

  1. src/main/java/com/baeldung/scheduling/ScheduledService.java
package com.example.scheduling;

import org.springframework.stereotype.Service;

/**
 * Service to be spied on by the integration test.
 * In a real application, this would contain complex logic.
 */
@Service
public class ScheduledService {

    private int executionCount = 0;

    // This is the method we want to verify was called by the scheduler.
    public void executeScheduledTask() {
        System.out.println("--- Executing scheduled task (Real Implementation) ---");
        executionCount++;
        // The actual business logic goes here
    }

    public int getExecutionCount() {
        return executionCount;
    }
}

  1. src/test/java/com/baeldung/scheduling/ScheduledServiceIntegrationTest.java
package com.example.scheduling;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ActiveProfiles;
import static org.mockito.Mockito.verify;
import java.util.concurrent.TimeUnit;


// The @SpringBootTest annotation automatically enables the JUnit 5 SpringExtension.

@SpringBootTest
@ActiveProfiles("test") // Ensures the test runs in a controlled environment
public class ScheduledServiceIntegrationTest {

    // The service that is the target of the spy
    @SpyBean // <-- FAILURE POINT
    private ScheduledService scheduledService;

    // A dependency that triggers the method call we want to spy on
    // Assuming this class triggers scheduledService.executeScheduledTask()
    @Autowired
    private SchedulerComponent scheduler; 

    // Note: You would likely need to configure a minimal @EnableScheduling setup
    // in your main application or a test configuration to ensure the scheduler runs.

    @Test
    void whenSchedulerRuns_thenScheduledTaskIsCalled() throws Exception {
        // --- 1. Give the scheduler time to run its task ---
        // This is necessary for asynchronous scheduling tests.
        TimeUnit.SECONDS.sleep(3);

        // --- 2. Verify the spied method was called (the fix confirmation) ---
        // If @SpyBean is working correctly with JUnit 5, this verification passes.
        // It verifies that the real method was executed at least once.
        verify(scheduledService).executeScheduledTask();
    }
}

Comment From: philwebb

@SpyBean was deprecated in 3.4 and finally removed in 4.0. The javadoc provides the replacement (org.springframework.test.context.bean.override.mockito.MockitoSpyBean) which is now part of Spring Framework.