Skip to content

@MockitoSpyBean requires existing bean instance to spy on #33935

Closed as not planned
@quaff

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.

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