Skip to content

@MockitoSpyBean requires existing bean instance to spy on #33935

Description

@quaff

Overview

When an existing bean of the required type is not discovered in the bean factory, Spring Boot's @SpyBean creates a new instance to spy on by instantiating the required type using its default constructor.

Whereas, @MockitoSpyBean requires that a bean of the required type exists and throws an IllegalStateException if an appropriate bean cannot be found.

Consequently, @MockitoSpyBean cannot be used to spy on a bean that does not exist.

Original Description

@MockitoSpyBean cannot spy concrete runtime bean type, but Spring Boot's @SpyBean can.

Example

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.4.0'
	id 'io.spring.dependency-management' version 'latest.release'
}

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

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}
package com.example;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@ContextConfiguration
@SpringJUnitConfig
public class SpyBeanTests {

	@MockitoSpyBean
	// @SpyBean works
	// @Autowired works
	SubTestService testService;

	@Test
	void test() {
		assertThat(testService.echo("test")).isEqualTo("test");
	}

	@Configuration
	static class Config {

		@Bean
		TestService testService() {
			return new SubTestService();
		}

	}

	static class SubTestService extends TestService {

	}

	static class TestService {
		String echo(String str) {
			return str;
		}
	}
}

Test fails with:

Caused by: java.lang.IllegalStateException: Unable to select a bean to override by wrapping: found 0 bean instances of type com.example.SpyBeanTests$SubTestService (as required by annotated field 'SpyBeanTests.testService')
	at org.springframework.test.context.bean.override.BeanOverrideBeanFactoryPostProcessor.wrapBean(BeanOverrideBeanFactoryPostProcessor.java:242)
	at org.springframework.test.context.bean.override.BeanOverrideBeanFactoryPostProcessor.registerBeanOverride(BeanOverrideBeanFactoryPostProcessor.java:113)
	at org.springframework.test.context.bean.override.BeanOverrideBeanFactoryPostProcessor.postProcessBeanFactory(BeanOverrideBeanFactoryPostProcessor.java:98)
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:363)
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:197)
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:791)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:609)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:221)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:110)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:212)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:225)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:152)
	... 19 more

The workaround is changing the @Bean method to return the most specific type, SubTestService in this example.

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

in: testIssues in the test modulestatus: supersededAn issue that has been superseded by another

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions