我有一个drools规则文件,它在规则中使用服务类。因此,一个规则的作用如下:
(1)返回值为空的值。
在使用@service和@Transactional(propagation=Propagation.SUPPORTS)注解的验证服务中,drools文件用于无状态知识库中,并且添加了应该在drool中使用的事实。完成此操作后,将调用session.execute(facts)并启动规则引擎。
为了测试这些规则,我想stub countryService.getCountryById()。使用mockito没有大问题。在其他使用drools设置的服务上也是这样做的,它运行得很好。但是在这个特殊的例子中,countryService没有被存根,我不知道为什么。在花了很多时间检查我的代码后,我发现有@缺少@Transaction使得mockito模拟countryservice没有任何问题,而使用@transactional则导致mockito注入模拟失败(没有任何错误或提示),因此使用了原始的countryservice对象。
我的问题是为什么这个注解会导致这个问题。为什么当@Transactional被设置时mockito不能注入mock?我注意到当我调试和检查countryService时mockito失败了当它被作为全局添加到drools会话中时我在调试窗口中检查countryService时看到了以下不同:
- 使用@transactional:国家/地区服务的值为国家/地区服务$$EnhancerByCGLIB$$b80dbb7b
- 没有@事务性:国家服务的值为国家服务$$EnhancerByMockitoWithCGLIB$$27f34dc1
此外,如果使用@transactional,则会找到countryservice方法getCountryById中的断点,调试器将在该断点处停止,但如果不使用@transactional,则会跳过断点,因为mockito会绕过它。
验证服务:
@Service
@Transactional(propagation=Propagation.SUPPORTS)
public class ValidationService
{
@Autowired
private CountryService countryService;
public void validateFields(Collection<Object> facts)
{
KnowledgeBase knowledgeBase = (KnowledgeBase)AppContext.getApplicationContext().getBean(knowledgeBaseName);
StatelessKnowledgeSession session = knowledgeBase.newStatelessKnowledgeSession();
session.setGlobal("countryService", countryService);
session.execute(facts);
}
和测试类:
public class TestForeignAddressPostalCode extends BaseTestDomainIntegration
{
private final Collection<Object> postalCodeMinLength0 = new ArrayList<Object>();
@Mock
protected CountryService countryService;
@InjectMocks
private ValidationService level2ValidationService;
@BeforeMethod(alwaysRun=true)
protected void setup()
{
// Get the object under test (here the determination engine)
level2ValidationService = (ValidationService) getAppContext().getBean("validationService");
// and replace the services as documented above.
MockitoAnnotations.initMocks(this);
ForeignAddress foreignAddress = new ForeignAddress();
foreignAddress.setCountryCode("7029");
foreignAddress.setForeignPostalCode("foreign");
// mock country to be able to return a fixed id
Country country = mock(Country.class);
foreignAddress.setLand(country);
doReturn(Integer.valueOf(1)).when(country).getId();
doReturn(country).when(countryService).getCountryById(anyInt());
ContextualAddressBean context = new ContextualAddressBean(foreignAddress, "", AddressContext.CORRESPONDENCE_ADDRESS);
postalCodeMinLength0.add(context);
}
@Test
public void PostalCodeMinLength0_ExpectError()
{
// Execute
level2ValidationService.validateFields(postalCodeMinLength0, null);
}
如果我想保留这个@transactional注解,但同时又能stub countryservice方法,你知道该怎么做吗?
敬祝商祺,
米迦勒
5条答案
按热度按时间64jmpszr1#
请注意,从Spring 4.3.1开始,ReflectionTestUtils应该自动解包代理。
即使您的
countryService
是使用@Transactional
,@Cacheable
...(即在运行时隐藏在代理后面)注解的,现在也应该可以工作了相关问题:SPR-14050
xxls0lw82#
发生的情况是ValidationService被 Package 在JdkDynamicAopProxy中,因此当Mockito将模拟注入到服务中时,它看不到任何要注入它们的字段。您需要执行以下两项操作之一:
代码示例:
oxcyiej73#
基于the answer of SuperSaiyen,我创建了一个嵌入式实用程序类,使其更简单且类型安全:
使用方法很简单,只需在测试方法的开头,用要注入模拟的bean和要模拟的对象的类调用方法
mockFieldOnBean(Object beanToInjectMock, Class<T> classToMock)
。示例:假设您有一个类型为
SomeService
的bean,它包含一个自动连接的beanSomeOtherService
,类似于;要在
SomeService
Bean上模拟someOtherService
,请使用以下命令:一切都应该正常工作。
nom7f22z4#
另一种解决方案是在Spring将所有东西连接在一起之前,将mock对象添加到Spring上下文中,这样在测试开始之前就已经注入了look对象。
@Primary
注解很重要,它确保新的模拟CountryService具有注入的最高优先级,从而取代普通的CountryService。但是,如果在多个位置注入类,这可能会产生意想不到的副作用。nr9pn0ug5#
Spring测试模块中存在一个名为AopTestUtils的Spring实用程序。
获取提供的候选对象的最终目标对象,不仅可以展开一个顶层代理,还可以展开任意数量的嵌套代理,如果提供的候选对象是Spring代理,则返回所有嵌套代理的最终目标;否则,将按原样返回候选项。
您可以注入一个mock或spy,并在测试期间取消代理该类以安排mock或验证