当前位置: 首页 > 面试题库 >

Transactional annotation avoids services being mocked

徐丰茂
2023-03-14
问题内容

我有一个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将模拟程序注入到服务中时,它看不到任何将其注入的字段。你需要执行以下两项操作之一:

  • 放弃启动Spring Application Context并仅测试Validation Service,迫使你模拟每个依赖项。
  • 或从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;
}


 类似资料:

相关阅读

相关文章

相关问答