2012-10-12 31 views
7

我有一个在规则中使用服务类的drools规则文件。所以一个规则做这样的事情:交易注释避免被模拟的服务

的eval(countryService.getCountryById(1)!= NULL)

在validationservice被注释与@Service和@Transactional(传播= Propagation.SUPPORTS)Drools的文件在无状态知识库中使用,并添加应该在流口水中使用的事实。一旦完成,会调用session.execute(facts)并启动规则引擎。

为了测试规则,我想存根countryOK.getCountryById()。使用mockito没有大问题。对于使用drools设置的其他服务完成此操作,并且它工作正常。然而在这个特殊的情况下,countryService并不存在,我不知道为什么。花了很多时间并检查我的代码之后,我发现在服务之上有@Transactional或者缺少这个注释会产生差异。缺乏@Transaction使得mockito模拟countryservice没有任何问题,因为@transactional导致mockito失败(没有任何错误或提示)注入模拟,以便使用原始的countryservice对象。

我的问题是为什么这个注解导致这个问题。为什么不能在@Transactional设置时注入mockito?我注意到,在的Mockito失败,当我调试和检查countryService当它被添加为全局Drools的会议上,我看到下面的差异,当我检查我的debugwindow的countryservice:

  • 与@事务:countryService具有值countryService $$ EnhancerByCGLIB $$ b80dbb7b

  • 而不@Transactional:countryService具有值countryService $$ EnhancerByMockitoWithCGLIB $$ 27f34dc1

此外与@t ransactional我在countryservice方法的断点getCountryById被找到并且调试器停在那个断点处,但没有@transactional,当mockito绕过它时,我的断点被跳过。

ValidationService:

@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 methodes?

问候,

迈克尔

+0

你能更确切地说明你为什么知道mockito失败的原因? 此外,虽然与问题无关,但您应该注意,嘲笑值并不是真正建议的,您应该自己创建一个值实例,也许在测试中使用自定义工厂或私有构造函数等等。 – Brice

+0

也可以显示更多的'BaseTestDomainIntegration',并且如果相关的话可能是spring配置。 – Brice

+0

嗨brice,我已经添加了更多信息。看到子弹 – Michael

回答

4

请告诉我发生的事情是你ValidationService被包裹在一个JdkDynamicAopProxy,所以当去的Mockito的嘲笑注入服务它不会看到任何字段,将它们注入。你需要做两件事情之一:

  • 开始放弃你的Spring应用上下文和测试只是 验证服务,迫使你嘲笑每个依赖。
  • 或从JdkDynamicAopProxy中打开您的实现,并自己处理注入模拟。

代码示例:

@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; 
} 

Blog entry on the issue

2

另一种解决方案是将模拟对象添加到前弹簧钢丝一切Spring上下文在一起,使之已经前注入你的测试开始。修改后的测试可能会是这个样子:

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(classes = { Application.class, MockConfiguration.class }) 
public class TestForeignAddressPostalCode extends BaseTestDomainIntegration 
{ 

    public static class MockConfiguration { 

     @Bean 
     @Primary 
     public CountryService mockCountryService() { 
     return mock(CountryService.class); 
     } 

    } 

    @Autowired 
    protected CountryService mockCountryService; 

    @Autowired 
    private ValidationService level2ValidationService; 

    @BeforeMethod(alwaysRun=true) 
    protected void setup() 
    { 

    // set up you mock stubs here 
    // ... 

@Primary注解是很重要的,确保您的新模拟CountryService有注射重中之重,取代了正常的一个。然而,如果班级在多个地方注射,这可能会产生意想不到的副作用。

1

基于the answer of SuperSaiyen,我创建了一个下拉式的实用工具类,使其更简单&类型安全:

import org.mockito.Mockito; 
import org.springframework.aop.framework.Advised; 
import org.springframework.aop.support.AopUtils; 
import org.springframework.test.util.ReflectionTestUtils; 

@SuppressWarnings("unchecked") 
public class SpringBeanMockUtil { 
    /** 
    * If the given object is a proxy, set the return value as the object being proxied, otherwise return the given 
    * object. 
    */ 
    private static <T> T unwrapProxy(T bean) { 
    try { 
     if (AopUtils.isAopProxy(bean) && bean instanceof Advised) { 
     Advised advised = (Advised) bean; 
     bean = (T) advised.getTargetSource().getTarget(); 
     } 
     return bean; 
    } 
    catch (Exception e) { 
     throw new RuntimeException("Could not unwrap proxy!", e); 
    } 
    } 

    public static <T> T mockFieldOnBean(Object beanToInjectMock, Class<T> classToMock) { 
    T mocked = Mockito.mock(classToMock); 
    ReflectionTestUtils.setField(unwrapProxy(beanToInjectMock), null, mocked, classToMock); 
    return mocked; 
    } 
} 

用法很简单,只需在您的测试方法的开始,调用该方法mockFieldOnBean(Object beanToInjectMock, Class<T> classToMock)与你想注入一个模拟的bean,以及应该被模拟的对象的类。例如:

比方说,你有一个类型为SomeService的bean,它拥有一个自动布线的豆SomeOtherService,类似于;

@Component 
public class SomeService { 
    @Autowired 
    private SomeOtherService someOtherService; 

    // some other stuff 
} 

要在SomeService豆嘲笑someOtherService,使用以下命令:

@RunWith(SpringJUnit4ClassRunner.class) 
public class TestClass { 

    @Autowired 
    private SomeService someService; 

    @Test 
    public void sampleTest() throws Exception { 
    SomeOtherService someOtherServiceMock = SpringBeanMockUtil.mockFieldOnBean(someService, SomeOtherService.class); 

    doNothing().when(someOtherServiceMock).someMethod(); 

    // some test method(s) 

    verify(someOtherServiceMock).someMethod(); 
    } 
} 

一切都应该工作,因为他们应该。

4

请注意,自Spring 4.3.1以来,ReflectionTestUtils应自动解包代理。所以

ReflectionTestUtils.setField(validationService, "countryService", countryService); 

现在应该即使你countryService@Transactional@Cacheable注释工作... ...(即,背后隐藏着在运行一个代理)

相关的问题:SPR-14050