Skip to content

Commit 7824171

Browse files
committed
Allow @SpyBean to work with @primary beans
Update `MockitoPostProcessor` so that `@SpyBean` will automatically pick the `@Primary` bean when multiple candidates exist. Fixes gh-7621
1 parent 87547f2 commit 7824171

File tree

4 files changed

+162
-10
lines changed

4 files changed

+162
-10
lines changed

spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import org.springframework.beans.factory.BeanFactoryAware;
3838
import org.springframework.beans.factory.BeanFactoryUtils;
3939
import org.springframework.beans.factory.FactoryBean;
40+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
41+
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
4042
import org.springframework.beans.factory.config.BeanDefinition;
4143
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
4244
import org.springframework.beans.factory.config.BeanPostProcessor;
@@ -250,7 +252,7 @@ private void registerSpy(ConfigurableListableBeanFactory beanFactory,
250252
createSpy(registry, definition, field);
251253
}
252254
else {
253-
registerSpies(definition, field, existingBeans);
255+
registerSpies(registry, definition, field, existingBeans);
254256
}
255257
}
256258

@@ -307,15 +309,41 @@ private void createSpy(BeanDefinitionRegistry registry, SpyDefinition definition
307309
registerSpy(definition, field, beanName);
308310
}
309311

310-
private void registerSpies(SpyDefinition definition, Field field,
311-
String[] existingBeans) {
312-
Assert.state(field == null || existingBeans.length == 1,
313-
"Unable to register spy bean " + definition.getTypeToSpy()
314-
+ " expected a single existing bean to replace but found "
315-
+ new TreeSet<String>(Arrays.asList(existingBeans)));
316-
for (String beanName : existingBeans) {
317-
registerSpy(definition, field, beanName);
312+
private void registerSpies(BeanDefinitionRegistry registry, SpyDefinition definition,
313+
Field field, String[] existingBeans) {
314+
ResolvableType type = definition.getTypeToSpy();
315+
try {
316+
if (ObjectUtils.isEmpty(existingBeans)) {
317+
throw new NoSuchBeanDefinitionException(type);
318+
}
319+
if (existingBeans.length > 1) {
320+
existingBeans = new String[] {
321+
determinePrimaryCandidate(registry, existingBeans, type) };
322+
}
323+
registerSpy(definition, field, existingBeans[0]);
324+
}
325+
catch (RuntimeException ex) {
326+
throw new IllegalStateException(
327+
"Unable to register spy bean " + definition.getTypeToSpy(), ex);
328+
}
329+
}
330+
331+
private String determinePrimaryCandidate(BeanDefinitionRegistry registry,
332+
String[] candidateBeanNames, ResolvableType type) {
333+
String primaryBeanName = null;
334+
for (String candidateBeanName : candidateBeanNames) {
335+
BeanDefinition beanDefinition = registry.getBeanDefinition(candidateBeanName);
336+
if (beanDefinition.isPrimary()) {
337+
if (primaryBeanName != null) {
338+
throw new NoUniqueBeanDefinitionException(type.resolve(),
339+
candidateBeanNames.length,
340+
"more than one 'primary' bean found among candidates: "
341+
+ Arrays.asList(candidateBeanNames));
342+
}
343+
primaryBeanName = candidateBeanName;
344+
}
318345
}
346+
return primaryBeanName;
319347
}
320348

321349
private void registerSpy(SpyDefinition definition, Field field, String beanName) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2012-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.test.mock.mockito;
18+
19+
import org.junit.Test;
20+
import org.junit.runner.RunWith;
21+
import org.mockito.internal.util.MockUtil;
22+
23+
import org.springframework.beans.factory.annotation.Autowired;
24+
import org.springframework.boot.test.mock.mockito.example.ExampleGenericStringServiceCaller;
25+
import org.springframework.boot.test.mock.mockito.example.SimpleExampleStringGenericService;
26+
import org.springframework.context.annotation.Bean;
27+
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.context.annotation.Import;
29+
import org.springframework.context.annotation.Primary;
30+
import org.springframework.test.context.junit4.SpringRunner;
31+
32+
import static org.assertj.core.api.Assertions.assertThat;
33+
import static org.mockito.Mockito.verify;
34+
35+
/**
36+
* Test {@link SpyBean} on a test class field can be used to inject new spy instances.
37+
*
38+
* @author Phillip Webb
39+
*/
40+
@RunWith(SpringRunner.class)
41+
public class SpyBeanOnTestFieldForMultipleExistingBeansIntegrationTests {
42+
43+
@SpyBean
44+
private SimpleExampleStringGenericService spy;
45+
46+
@Autowired
47+
private ExampleGenericStringServiceCaller caller;
48+
49+
@Test
50+
public void testSpying() throws Exception {
51+
assertThat(this.caller.sayGreeting()).isEqualTo("I say two");
52+
assertThat(new MockUtil().getMockName(this.spy).toString()).isEqualTo("two");
53+
verify(this.spy).greeting();
54+
}
55+
56+
@Configuration
57+
@Import(ExampleGenericStringServiceCaller.class)
58+
static class Config {
59+
60+
@Bean
61+
public SimpleExampleStringGenericService one() {
62+
return new SimpleExampleStringGenericService("one");
63+
}
64+
65+
@Bean
66+
@Primary
67+
public SimpleExampleStringGenericService two() {
68+
return new SimpleExampleStringGenericService("two");
69+
}
70+
71+
}
72+
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2012-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.test.mock.mockito.example;
18+
19+
/**
20+
* Example bean for mocking tests that calls {@link ExampleGenericService}.
21+
*
22+
* @author Phillip Webb
23+
*/
24+
public class ExampleGenericStringServiceCaller {
25+
26+
private final ExampleGenericService<String> stringService;
27+
28+
public ExampleGenericStringServiceCaller(
29+
ExampleGenericService<String> stringService) {
30+
this.stringService = stringService;
31+
}
32+
33+
public ExampleGenericService<String> getStringService() {
34+
return this.stringService;
35+
}
36+
37+
public String sayGreeting() {
38+
return "I say " + this.stringService.greeting();
39+
}
40+
41+
}

spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleStringGenericService.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,19 @@
2323
*/
2424
public class SimpleExampleStringGenericService implements ExampleGenericService<String> {
2525

26+
private final String greeting;
27+
28+
public SimpleExampleStringGenericService() {
29+
this("simple");
30+
}
31+
32+
public SimpleExampleStringGenericService(String greeting) {
33+
this.greeting = greeting;
34+
}
35+
2636
@Override
2737
public String greeting() {
28-
return "simple";
38+
return this.greeting;
2939
}
3040

3141
}

0 commit comments

Comments
 (0)