spock icon indicating copy to clipboard operation
spock copied to clipboard

Unable to mock a Spring primary bean when other instances are available

Open konradczajka opened this issue 3 years ago • 2 comments

Describe the bug

In larger Spring Boot's codebases it's not uncommon to have multiple beans of the same type where one of them is marked as @Primary.

Attempting to mock such bean with @SpringBean doesn't work as Spock Spring requires exactly one mocked bean's instance in the context (regardless of whether one of them is marked as primary).

This is an unnecessary limitation, especially that spring-boot-starter-test doesn't pose such limit when using @MockBean.

You can find a sample project presenting this issue with tests written using both SpringBean and MockBean here: https://github.com/konradczajka/spock-spring-issue

The problem seems to be located in SpockMockPostprocessor which doesn't checks if one of located beans is marked as primary. I'd gladly provide a PR with a solution which seems to be working locally (which is highly inspired by the spring-boot-starter-test's code)

To Reproduce

With context configured as follows:

@Bean
Service2 service2NotPrimary() {
    new Service2()
}

@Primary
@Bean
Service2 service2Primary() {
    new Service2()
}

@Bean
IService1 service1(Service2 service2) {
    return new Service1(service2)
}

and a test like this:

@SpringBean
Service2 service2 = Mock() {
    generateQuickBrownFox() >> "blubb"
}

@Autowired
Service1 service1

def "injection with stubbing works"() {
    expect:
    service1.generateString() == "blubb"
}

Expected behavior

Passing test

Actual behavior

The test fails with a message similar to this:

Unable to register mock beanService2 expected a single matching bean to replace but found [service2Primary, service2NotPrimary]

Java version

18, but different versions don't affect the outcome

Buildtool version

18, but different versions don't affect the outcome

What operating system are you using

Linux

Dependencies

plugins {
	id 'org.springframework.boot' version '2.7.2'
	id 'io.spring.dependency-management' version '1.0.12.RELEASE'
	id 'java'
	id 'groovy'
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.spockframework:spock-core:2.1-groovy-3.0'
	testImplementation 'org.spockframework:spock-spring:2.1-groovy-3.0'
}

Additional context

The same test written in Java/Mockito with spring-boot-starter-test and its @MockBean works as intended.

konradczajka avatar Jul 26 '22 18:07 konradczajka

This may not be a bug per se (more like a missing feature) but I'm not authorized to remove the label :(

konradczajka avatar Jul 27 '22 14:07 konradczajka

The relevant logic in MockitoPostProcessor was added after I did the implementation for Spock based on it.

leonard84 avatar Jul 28 '22 18:07 leonard84