模拟 final class 并将其注入自动装配的数据成员并克服 postConstruct 方法调用
Mock final class and inject it to autowired data member and overcome postConstruct method call
我想使用自动装配的最终 class 对象以及另一个具有 @PostConstruct 方法的自动装配的 class 对 java class 进行单元测试。虽然可以单独测试它们,但我无法将它们组合在一起。
这个问题是 injecting mockito mocks into spring bean
问题的延伸
待测代码
public class A {
@Autowired
private FinalClass serviceClient;
@Autowired
private ClassWithPostConstructor resourceVerifier;
//no setters or constructors
public String useBothFinalClassAndClassWithPostConstructor() {
//logic to be tested
}
}
工作测试class
@RunWith(PowerMockRunner.class)
@PrepareForTest(FinalClass.class)
public class ATest {
//@org.mockito.Mock //fails to mock final class
@org.powermock.api.easymock.annotation.Mock
private FinalClass serviceClient;
@org.powermock.api.easymock.annotation.Mock
private ClassWithPostConstructor resourceVerifier;
//other mock objects required for mocking the services
//@InjectMocks //fails since mocking final class
private A a;
@Before
public void init() {
a = new A();
//working snippet with setters created in A and without @Autowired here within the test
serviceClient = PowerMock.create(FinalClass.class);
a.setServiceClient(serviceClient);
resourceVerifier = PowerMock.create(ClassWithPostConstructor.class);
a.setClassWithPostConstructor(resourceVerifier);
}
@Test
public void testTheMethodUsingExpectAndVerify() {
//test the functionality here
EasyMock.expect(serviceClient.callService()).andReturn("someMock");
EasyMock.expect(resourceVerifier.verifyFn()).andReturn("success");
PowerMock.replayAll();
A.useBothFinalClassAndClassWithPostConstructor();
PowerMock.verifyAll();
}
}
以上代码适用于文件中设置器的需要
预期测试 class
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:spring-configuration/unit-testing-config.xml"})
@PrepareForTest(FinalClass.class)
public class ATest {
@Autowired
private FinalClass serviceClient;
@Autowired
private ClassWithPostConstructor resourceVerifier;
//other mock objects required for mocking the services
private A a;
@Before
public void init() {
a = new A();
}
@Test
public void testTheMethodUsingExpectAndVerify() {
//test the functions here
EasyMock.expect(serviceClient.callService()).andReturn("someMock");
EasyMock.expect(resourceVerifier.verifyFn()).andReturn("success");
PowerMock.replayAll();
A.useBothFinalClassAndClassWithPostConstructor();
PowerMock.verifyAll();
}
}
//spring-configuration/unit-testing-config.xml
//same error even on customer factory
<bean id="resourceVerifier" class="org.powermock.api.easymock.PowerMock" factory-method="createMock">
<constructor-arg type="java.lang.Class" value="com.company...resourceVerifier" />
</bean>
<bean id="resourceVerifier" class="org.powermock.api.easymock.PowerMock" factory-method="createMock">
<constructor-arg type="java.lang.Class" value="com.company...serviceClient" />
</bean>
上面的代码片段模拟了 finalClass 但调用了 ResourceVerifier.class 的 @PostConstructor - 这里应该做什么来克服这个调用。
调查
- 可以使用 @InjectMocks 测试自动装配的文件,而无需 spring 上下文配置。
- @InjectMock 静态和最终字段静默失败,失败时,它也不会注入其他模拟。
- 可以使用 PowerMock 的 createMock 模拟最终 class 和 运行 使用 PowerMockRunner[=81= 的测试] 和 @PrepareForTest。但这需要新的不必要的 setters 来为 @Autowired 字段注入模拟。
- MockitoAnnotations.@Mock 不能很好地与 PowerMock 一起工作(尤其是在模拟最终 class 对象时)并且可以通过 EasyMock.Annotations.@Mock
- EasyMock 和 PowerMock 没有 @InjectMocks 注释来注入模拟尽可能通过 Mockito(会在几秒钟内解决问题)。
- 可以通过 SpringJUnit4Runner 和单独的单元测试 @ContextConfiguration[=82 注入自动装配的 spring bean =]
- 可以 运行 使用 PowerMockRunner 和 SpringJUnit4Runner 使用 PowerMockRunnerDelegate
- 我知道如果在代码中模拟 @PostConstruct 方法将不会自动执行,而不是使用 spring bean 创建和注入。
- 如果编写了包装器工厂 bean class 并用于创建模拟,它会自动注入,但也会同时调用 @PostConstruct 方法。
- 不可能依赖 Springockito,因为它在现阶段不可靠。
但其中 none 有效,因为用例是所有这些的组合。
可能的解决方案
- 删除 @Autowired 字段并使用 Setter 注入 以便可以通过使用 PowerMock 进行正常模拟(已测试work) - 但这是一个约定俗成的外部团队包 - 我应该尽力坚持下去。
- 或者将@Autowired 设置为 setter 或构造函数
备选方案?
我不认为 classes 需要重组,因为它们服务于它们的目的并且设计得很好。
- 任何其他不需要保持对正在测试的 class 的手的方法 - 如果我没有修改此 class 的权限怎么办?即,纯测试库依赖解决方案。
- 不确定 PowerMockito 是否可行?还没有尝试过 PowerMockito 与 PowerMock.
的组合
嗯,
Not sure whether it is possible by PowerMockito? Haven't tried the combination of PowerMockito with PowerMock.
在我看来,您脑子里一片混乱,并且误解了 PowerMock/Mockito 和 EasyMock。
你不应该同时使用 PowerMockito
和 PowerMock
,因为这两个 类 对 PowerMock 友好 API 用于两个不同的模拟框架:EasyMock and Mockito.并且没有理由同时使用它们。
当然这需要工作
//@org.mockito.Mock //fails to mock final class
@org.powermock.api.easymock.annotation.Mock
private FinalClass serviceClient;
@org.powermock.api.easymock.annotation.Mock
private ClassWithPostConstructor resourceVerifier;
//other mock objects required for mocking the services
//@InjectMocks //fails since mocking final class
private A a;
因为,您通过 EasyMock API 减速并创建模拟,但尝试使用 Mockito Annotation 注入它。
您只需要选择一个 Mocking Framework 并使用合适的 API 即可。目前,Mockito + PowerMockito(PowerMock API for Mockito)更符合您的要求。
您可以完整示例它如何在 PowerMock github
上工作
@RunWith(PowerMockRunner.class)
@PrepareForTest(FinalClass.class)
public class SpringInjectFinalClassExampleTest {
@Mock
private FinalClass finalClass;
@InjectMocks
private MyBean myBean = new MyBean();;
@Test
public void testInjectFinalClass() {
final String value = "What's up?";
when(finalClass.sayHello()).thenReturn(value);
assertEquals(value, myBean.sayHello());
}
}
我想使用自动装配的最终 class 对象以及另一个具有 @PostConstruct 方法的自动装配的 class 对 java class 进行单元测试。虽然可以单独测试它们,但我无法将它们组合在一起。
这个问题是 injecting mockito mocks into spring bean
问题的延伸待测代码
public class A {
@Autowired
private FinalClass serviceClient;
@Autowired
private ClassWithPostConstructor resourceVerifier;
//no setters or constructors
public String useBothFinalClassAndClassWithPostConstructor() {
//logic to be tested
}
}
工作测试class
@RunWith(PowerMockRunner.class)
@PrepareForTest(FinalClass.class)
public class ATest {
//@org.mockito.Mock //fails to mock final class
@org.powermock.api.easymock.annotation.Mock
private FinalClass serviceClient;
@org.powermock.api.easymock.annotation.Mock
private ClassWithPostConstructor resourceVerifier;
//other mock objects required for mocking the services
//@InjectMocks //fails since mocking final class
private A a;
@Before
public void init() {
a = new A();
//working snippet with setters created in A and without @Autowired here within the test
serviceClient = PowerMock.create(FinalClass.class);
a.setServiceClient(serviceClient);
resourceVerifier = PowerMock.create(ClassWithPostConstructor.class);
a.setClassWithPostConstructor(resourceVerifier);
}
@Test
public void testTheMethodUsingExpectAndVerify() {
//test the functionality here
EasyMock.expect(serviceClient.callService()).andReturn("someMock");
EasyMock.expect(resourceVerifier.verifyFn()).andReturn("success");
PowerMock.replayAll();
A.useBothFinalClassAndClassWithPostConstructor();
PowerMock.verifyAll();
}
}
以上代码适用于文件中设置器的需要
预期测试 class
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:spring-configuration/unit-testing-config.xml"})
@PrepareForTest(FinalClass.class)
public class ATest {
@Autowired
private FinalClass serviceClient;
@Autowired
private ClassWithPostConstructor resourceVerifier;
//other mock objects required for mocking the services
private A a;
@Before
public void init() {
a = new A();
}
@Test
public void testTheMethodUsingExpectAndVerify() {
//test the functions here
EasyMock.expect(serviceClient.callService()).andReturn("someMock");
EasyMock.expect(resourceVerifier.verifyFn()).andReturn("success");
PowerMock.replayAll();
A.useBothFinalClassAndClassWithPostConstructor();
PowerMock.verifyAll();
}
}
//spring-configuration/unit-testing-config.xml
//same error even on customer factory
<bean id="resourceVerifier" class="org.powermock.api.easymock.PowerMock" factory-method="createMock">
<constructor-arg type="java.lang.Class" value="com.company...resourceVerifier" />
</bean>
<bean id="resourceVerifier" class="org.powermock.api.easymock.PowerMock" factory-method="createMock">
<constructor-arg type="java.lang.Class" value="com.company...serviceClient" />
</bean>
上面的代码片段模拟了 finalClass 但调用了 ResourceVerifier.class 的 @PostConstructor - 这里应该做什么来克服这个调用。
调查
- 可以使用 @InjectMocks 测试自动装配的文件,而无需 spring 上下文配置。
- @InjectMock 静态和最终字段静默失败,失败时,它也不会注入其他模拟。
- 可以使用 PowerMock 的 createMock 模拟最终 class 和 运行 使用 PowerMockRunner[=81= 的测试] 和 @PrepareForTest。但这需要新的不必要的 setters 来为 @Autowired 字段注入模拟。
- MockitoAnnotations.@Mock 不能很好地与 PowerMock 一起工作(尤其是在模拟最终 class 对象时)并且可以通过 EasyMock.Annotations.@Mock
- EasyMock 和 PowerMock 没有 @InjectMocks 注释来注入模拟尽可能通过 Mockito(会在几秒钟内解决问题)。
- 可以通过 SpringJUnit4Runner 和单独的单元测试 @ContextConfiguration[=82 注入自动装配的 spring bean =]
- 可以 运行 使用 PowerMockRunner 和 SpringJUnit4Runner 使用 PowerMockRunnerDelegate
- 我知道如果在代码中模拟 @PostConstruct 方法将不会自动执行,而不是使用 spring bean 创建和注入。
- 如果编写了包装器工厂 bean class 并用于创建模拟,它会自动注入,但也会同时调用 @PostConstruct 方法。
- 不可能依赖 Springockito,因为它在现阶段不可靠。
但其中 none 有效,因为用例是所有这些的组合。
可能的解决方案
- 删除 @Autowired 字段并使用 Setter 注入 以便可以通过使用 PowerMock 进行正常模拟(已测试work) - 但这是一个约定俗成的外部团队包 - 我应该尽力坚持下去。
- 或者将@Autowired 设置为 setter 或构造函数
备选方案?
我不认为 classes 需要重组,因为它们服务于它们的目的并且设计得很好。
- 任何其他不需要保持对正在测试的 class 的手的方法 - 如果我没有修改此 class 的权限怎么办?即,纯测试库依赖解决方案。
- 不确定 PowerMockito 是否可行?还没有尝试过 PowerMockito 与 PowerMock. 的组合
嗯,
Not sure whether it is possible by PowerMockito? Haven't tried the combination of PowerMockito with PowerMock.
在我看来,您脑子里一片混乱,并且误解了 PowerMock/Mockito 和 EasyMock。
你不应该同时使用 PowerMockito
和 PowerMock
,因为这两个 类 对 PowerMock 友好 API 用于两个不同的模拟框架:EasyMock and Mockito.并且没有理由同时使用它们。
当然这需要工作
//@org.mockito.Mock //fails to mock final class
@org.powermock.api.easymock.annotation.Mock
private FinalClass serviceClient;
@org.powermock.api.easymock.annotation.Mock
private ClassWithPostConstructor resourceVerifier;
//other mock objects required for mocking the services
//@InjectMocks //fails since mocking final class
private A a;
因为,您通过 EasyMock API 减速并创建模拟,但尝试使用 Mockito Annotation 注入它。
您只需要选择一个 Mocking Framework 并使用合适的 API 即可。目前,Mockito + PowerMockito(PowerMock API for Mockito)更符合您的要求。
您可以完整示例它如何在 PowerMock github
上工作@RunWith(PowerMockRunner.class)
@PrepareForTest(FinalClass.class)
public class SpringInjectFinalClassExampleTest {
@Mock
private FinalClass finalClass;
@InjectMocks
private MyBean myBean = new MyBean();;
@Test
public void testInjectFinalClass() {
final String value = "What's up?";
when(finalClass.sayHello()).thenReturn(value);
assertEquals(value, myBean.sayHello());
}
}