我有一个drools规则文件,该文件在规则中使用服务类。因此,一条规则就是这样的:
eval(countryService.getCountryById(1)!= null)
在以@service和@Transactional(propagation = Propagation.SUPPORTS)注释的验证服务中,在无状态知识库中使用了drools文件,并添加了应该在drool中使用的事实。完成后,将调用session.execute(facts)并启动规则引擎。
为了测试规则,我想对countryService.getCountryById()进行存根。使用Mockito没什么大问题。对于其他使用流口水设置的服务也做了此操作,并且效果很好。但是在这种特殊情况下,countryService没有被打断,我也不知道为什么。花了很多时间并检查了我的代码后,我发现在服务上方使用@Transactional或缺少此批注会有所不同。缺少@Transaction使得Mockito可以毫无问题地模拟countryservice,将@transactional放置在适当的位置会导致Mockito失败(没有任何错误或提示),因此无法注入原始模拟的countryservice对象。
我的问题是为什么此注释会导致此问题。设置@Transactional时,为什么不能嘲笑模拟?我已经注意到,嘲笑失败了,因为当我调试并检查countryService并将其作为全局变量添加到drools会话时,我在调试窗口中检查countryservice时看到以下区别:
使用@transactional:countryService的值为CountryService $$ EnhancerByCGLIB $$ b80dbb7b
没有@transactional:countryService的值是CountryService $$ EnhancerByMockitoWithCGLIB $$ 27f34dc1
另外,使用@transactional时,会在countryservice方法中找到我的断点getCountryById,调试器将在该断点处停止,但是没有@transactional时,我的断点将被跳过,因为嘲笑会绕过它。
验证服务:
@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批注但又能够对countryservice方法进行存根,该怎么办?
问候,
发生的事情是你的ValidationService被包装在JdkDynamicAopProxy中,因此,当Mockito将模拟程序注入到服务中时,它看不到任何将其注入的字段。你需要执行以下两项操作之一:
@Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); ValidationService validationService = (ValidationService) unwrapProxy(level2ValidationService); ReflectionTestUtils.setField(validationService, "countryService", countryService); } public static final Object unwrapProxy(Object bean) throws Exception { /* * If the given object is a proxy, set the return value as the object * being proxied, otherwise return the given object. */ if (AopUtils.isAopProxy(bean) && bean instanceof Advised) { Advised advised = (Advised) bean; bean = advised.getTargetSource().getTarget(); } return bean; }
请注意,从Spring 4.3.1开始,ReflectionTestUtils应该自动打开代理。所以
ReflectionTestUtils.setField(validationService, "countryService", countryService);
现在,即使你countryService使用@Transactional,@Cacheable...(即,在运行时隐藏在代理后面)也应该可以正常工作
countryService
@Transactional
@Cacheable...