Skip to content

Commit e587753

Browse files
committed
Introduce @⁠EasyMockBean bean override example
This commit introduces example support for a custom @⁠EasyMockBean annotation that allows tests to use EasyMock as the mocking framework for bean overrides in a test's ApplicationContext. The point of this exercise is to ensure that it is possible for third parties to introduce bean override support for mocking frameworks other than Mockito, and that they can do so with the APIs currently in place. Closes gh-33562
1 parent 6c2cba5 commit e587753

9 files changed

+394
-0
lines changed

framework-platform/framework-platform.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ dependencies {
115115
api("org.codehaus.jettison:jettison:1.5.4")
116116
api("org.crac:crac:1.4.0")
117117
api("org.dom4j:dom4j:2.1.4")
118+
api("org.easymock:easymock:5.4.0")
118119
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.7")
119120
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
120121
api("org.eclipse:yasson:2.0.4")

spring-test/spring-test.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ dependencies {
7777
exclude group: "commons-logging", module: "commons-logging"
7878
}
7979
testImplementation("org.awaitility:awaitility")
80+
testImplementation("org.easymock:easymock")
8081
testImplementation("org.hibernate:hibernate-core-jakarta")
8182
testImplementation("org.hibernate:hibernate-validator")
8283
testImplementation("org.hsqldb:hsqldb")

spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java

+1
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ void endToEndTestsForTestBeanOverrideTestClasses() {
164164
@Test
165165
void endToEndTestsForSelectedTestClasses() {
166166
List<Class<?>> testClasses = List.of(
167+
org.springframework.test.context.bean.override.easymock.EasyMockBeanIntegrationTests.class,
167168
org.springframework.test.context.bean.override.mockito.MockitoBeanForByNameLookupIntegrationTests.class,
168169
org.springframework.test.context.junit4.SpringJUnit4ClassRunnerAppCtxTests.class,
169170
org.springframework.test.context.junit4.ParameterizedDependencyInjectionTests.class
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2002-2024 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+
* https://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.test.context.bean.override.easymock;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.easymock.MockType;
26+
27+
import org.springframework.core.annotation.AliasFor;
28+
import org.springframework.test.context.bean.override.BeanOverride;
29+
30+
/**
31+
* {@code @EasyMockBean} is a field-level annotation that can be used in a test
32+
* class to signal that a bean should be replaced with an {@link org.easymock.EasyMock
33+
* EasyMock} mock.
34+
*
35+
* @author Sam Brannen
36+
* @since 6.2
37+
*/
38+
@Target(ElementType.FIELD)
39+
@Retention(RetentionPolicy.RUNTIME)
40+
@Documented
41+
@BeanOverride(EasyMockBeanOverrideProcessor.class)
42+
public @interface EasyMockBean {
43+
44+
/**
45+
* Alias for {@link #name}.
46+
*/
47+
@AliasFor("name")
48+
String value() default "";
49+
50+
/**
51+
* The name of the bean to mock.
52+
* <p>Defaults to an empty string to denote that the name of the annotated
53+
* field should be used as the bean name.
54+
*/
55+
@AliasFor("value")
56+
String name() default "";
57+
58+
/**
59+
* The {@link MockType} to use when creating the mock.
60+
* <p>Defaults to {@link MockType#STRICT}.
61+
*/
62+
MockType mockType() default MockType.STRICT;
63+
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright 2002-2024 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+
* https://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.test.context.bean.override.easymock;
18+
19+
import org.easymock.EasyMockSupport;
20+
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
21+
import org.junit.jupiter.api.Order;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.TestMethodOrder;
24+
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.context.ApplicationContext;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.test.context.TestExecutionListeners;
30+
import org.springframework.test.context.bean.override.example.ExampleService;
31+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
32+
33+
import static org.assertj.core.api.Assertions.assertThat;
34+
import static org.easymock.EasyMock.expect;
35+
import static org.easymock.EasyMock.replay;
36+
import static org.easymock.EasyMock.reset;
37+
import static org.springframework.test.context.TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS;
38+
39+
/**
40+
* Integration tests for {@link EasyMockBean @EasyMockBean}.
41+
*
42+
* @author Sam Brannen
43+
* @since 6.2
44+
*/
45+
@SpringJUnitConfig
46+
@TestExecutionListeners(listeners = EasyMockResetTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS)
47+
@TestMethodOrder(OrderAnnotation.class)
48+
public class EasyMockBeanIntegrationTests {
49+
50+
@Autowired
51+
ApplicationContext ctx;
52+
53+
@EasyMockBean
54+
ExampleService service;
55+
56+
@Test
57+
@Order(1)
58+
void test1() {
59+
assertThat(ctx.getBean("service", ExampleService.class))
60+
.satisfies(this::assertIsEasyMock)
61+
.isSameAs(service);
62+
63+
// Before mock setup
64+
assertThat(service.greeting()).isNull();
65+
reset(service);
66+
67+
// After mock setup
68+
expect(service.greeting()).andReturn("mocked");
69+
replay(service);
70+
assertThat(service.greeting()).isEqualTo("mocked");
71+
}
72+
73+
@Test
74+
@Order(2)
75+
void test2() {
76+
assertThat(ctx.getBean("service", ExampleService.class))
77+
.satisfies(this::assertIsEasyMock)
78+
.isSameAs(service);
79+
80+
// Before mock setup
81+
assertThat(service.greeting()).isNull();
82+
reset(service);
83+
84+
// After mock setup
85+
expect(service.greeting()).andReturn("mocked");
86+
replay(service);
87+
assertThat(service.greeting()).isEqualTo("mocked");
88+
}
89+
90+
91+
private void assertIsEasyMock(Object obj) {
92+
assertThat(EasyMockSupport.isAMock(obj)).as("is EasyMock mock").isTrue();
93+
}
94+
95+
96+
@Configuration(proxyBeanMethods = false)
97+
static class Config {
98+
99+
@Bean
100+
ExampleService service() {
101+
return () -> "enigma";
102+
}
103+
}
104+
105+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2002-2024 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+
* https://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.test.context.bean.override.easymock;
18+
19+
import java.lang.reflect.Field;
20+
21+
import org.easymock.EasyMock;
22+
import org.easymock.MockType;
23+
24+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
25+
import org.springframework.beans.factory.config.BeanDefinition;
26+
import org.springframework.beans.factory.config.SingletonBeanRegistry;
27+
import org.springframework.core.ResolvableType;
28+
import org.springframework.lang.Nullable;
29+
import org.springframework.test.context.bean.override.OverrideMetadata;
30+
31+
import static org.springframework.test.context.bean.override.BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION;
32+
33+
/**
34+
* {@link OverrideMetadata} that provides support for {@link EasyMockBean @EasyMockBean}.
35+
*
36+
* @author Sam Brannen
37+
* @since 6.2
38+
*/
39+
class EasyMockBeanOverrideMetadata extends OverrideMetadata {
40+
41+
private final MockType mockType;
42+
43+
44+
EasyMockBeanOverrideMetadata(Field field, Class<?> typeToOverride, @Nullable String beanName,
45+
MockType mockType) {
46+
47+
super(field, ResolvableType.forClass(typeToOverride), beanName, REPLACE_OR_CREATE_DEFINITION);
48+
this.mockType = mockType;
49+
}
50+
51+
52+
@Override
53+
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition,
54+
@Nullable Object existingBeanInstance) {
55+
56+
Class<?> typeToMock = getBeanType().getRawClass();
57+
return EasyMock.mock(beanName, this.mockType, typeToMock);
58+
}
59+
60+
@Override
61+
protected void track(Object mock, SingletonBeanRegistry singletonBeanRegistry) {
62+
getEasyMockBeans(singletonBeanRegistry).add(mock);
63+
}
64+
65+
private EasyMockBeans getEasyMockBeans(SingletonBeanRegistry singletonBeanRegistry) {
66+
String className = EasyMockBeans.class.getName();
67+
EasyMockBeans easyMockBeans = null;
68+
try {
69+
easyMockBeans = (EasyMockBeans) singletonBeanRegistry.getSingleton(className);
70+
}
71+
catch (NoSuchBeanDefinitionException ignored) {
72+
}
73+
if (easyMockBeans == null) {
74+
easyMockBeans = new EasyMockBeans();
75+
singletonBeanRegistry.registerSingleton(className, easyMockBeans);
76+
}
77+
return easyMockBeans;
78+
}
79+
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2002-2024 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+
* https://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.test.context.bean.override.easymock;
18+
19+
import java.lang.annotation.Annotation;
20+
import java.lang.reflect.Field;
21+
22+
import org.springframework.test.context.bean.override.BeanOverrideProcessor;
23+
import org.springframework.test.context.bean.override.OverrideMetadata;
24+
import org.springframework.util.StringUtils;
25+
26+
/**
27+
* {@link BeanOverrideProcessor} that provides support for {@link EasyMockBean @EasyMockBean}.
28+
*
29+
* @author Sam Brannen
30+
* @since 6.2
31+
*/
32+
class EasyMockBeanOverrideProcessor implements BeanOverrideProcessor {
33+
34+
@Override
35+
public OverrideMetadata createMetadata(Annotation annotation, Class<?> testClass, Field field) {
36+
EasyMockBean easyMockBean = (EasyMockBean) annotation;
37+
String beanName = (StringUtils.hasText(easyMockBean.name()) ? easyMockBean.name() : field.getName());
38+
return new EasyMockBeanOverrideMetadata(field, field.getType(), beanName, easyMockBean.mockType());
39+
}
40+
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2002-2024 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+
* https://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.test.context.bean.override.easymock;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import org.easymock.EasyMock;
23+
24+
/**
25+
* @author Sam Brannen
26+
* @since 6.2
27+
*/
28+
class EasyMockBeans {
29+
30+
private final List<Object> beans = new ArrayList<>();
31+
32+
void add(Object bean) {
33+
this.beans.add(bean);
34+
}
35+
36+
void resetAll() {
37+
this.beans.forEach(EasyMock::reset);
38+
}
39+
40+
}

0 commit comments

Comments
 (0)