gpt4 book ai didi

spring - 事务注解避免服务被 mock

转载 作者:行者123 更新时间:2023-12-04 05:04:16 30 4
gpt4 key购买 nike

我有一个流口水规则文件,它在规则中使用服务类。所以一个规则做这样的事情:

eval(countryService.getCountryById(1) != null)

在使用 @service 和 @Transactional(propagation=Propagation.SUPPORTS) 注释的验证服务中,drools 文件用于无状态知识库中,并添加了应在 drool 中使用的事实。一旦完成,将调用 session.execute(facts) 并启动规则引擎。

为了测试规则,我想 stub countryService.getCountryById()。使用 mockito 没有什么大问题。为其他使用 drools 设置的服务完成此操作,并且效果很好。但是在这种特殊情况下, countryService 没有被 stub ,我不知道为什么。在花了很多时间检查我的代码后,我发现在服务上方使用 @Transactional 或缺少此注释会有所不同。缺少@Transaction 使得mockito 可以毫无问题地模拟countryservice,而使用@transactional 会导致mockito 失败(没有任何错误或提示)注入(inject)模拟,因此使用了原始的countryservice 对象。

我的问题是为什么这个注释会导致这个问题。为什么在设置@Transactional 时不能模拟注入(inject)模拟?我注意到,当我调试和检查 countryService 时,mockito 失败了,当它作为全局添加到 drools session 时,当我在调试窗口中检查 countryservice 时,我看到以下差异:

  • 使用@transactional:countryService 的值为 CountryService$$EnhancerByCGLIB$$b80dbb7b
  • 没有@transactional:countryService 的值为 CountryService$$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 方法,知道该怎么做吗?

    问候,

    迈克尔

    最佳答案

    基于 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)使用要在其上注入(inject)模拟的 bean,以及应该模拟的对象的类。例子:

    假设你有一个类型为 SomeService 的 bean它拥有一个 Autowiring 的 bean SomeOtherService , 就像是;
    @Component
    public class SomeService {
    @Autowired
    private SomeOtherService someOtherService;

    // some other stuff
    }

    模拟 someOtherServiceSomeService bean ,使用以下:
    @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();
    }
    }

    一切都应该正常工作。

    关于spring - 事务注解避免服务被 mock ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12857981/

    30 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com