【问题标题】:Mocking Spring bean's method behavior breaks aspects模拟 Spring bean 的方法行为打破了方面
【发布时间】:2016-02-04 14:33:44
【问题描述】:

我搜索了 SO,发现了一堆看起来相似但不完全相同的其他问题,所以我会再问一个。

我有 Spring 应用程序并说我创建了自定义方面(寻找 CatchMe 注释)以特定方式记录异常。我想通过模拟我的 Spring @Service 类方法之一的行为来测试方面,以便在调用它时抛出异常。然后在另一种方法中,使用我的自定义注释@CatchMe 进行注释,我调用第一种方法。我期望发生的是被记录的异常。不幸的是,抛出了异常,但没有触发方面。那么如何使用 Mockito 在此测试中触发方面?

注意:我已经检查了这些(以及更多):

但其中大多数是与控制器相关的,而不是与服务相关的,我只想测试服务。

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {BeanConfig.class})
public class MyServiceTest {

    @Autowired
    @InjectMocks
    private MyService service;

    @Mock
    private MyServiceDependency serviceDep;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        ReflectionTestUtils.setField(service, "serviceDep", serviceDep);
    }

    @Test
    public void test() {
        when(serviceDep.process()).thenAnswer(new Answer<Object>() {

                @Override
                public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                    throw new Exception("Sample message.");
                }

            });

        service.execute();
    }
}

服务

@Service
public class MyService {

    @Autowired
    private MyServiceDependency serviceDep;

    @CatchMe
    public void execute() {
        serviceDep.process();
    }
}


@Service
public class MyServiceDependency {

    public Object process() {
        // may throw exception here
    }
}

配置和方面

@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = {"com.example.services"})
public class BeanConfig { .. }


@Aspect
@Component
public class CatchMeAspect {

    @Around("@annotation(CatchMe)")
    public Object catchMe(final ProceedingJoinPoint pjp) throws Throwable {
        try {
            pjp.proceed();
        } catch (Throwable t) {
            // fency log
        }

    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CatchMe {}

编辑:该功能有效,但我想通过测试对其进行验证。

【问题讨论】:

  • 如果只是重试,您想使用spring-retry 而不是重新发明轮子。为什么你的测试类是@Configuration 所有注释什么都不做,因为它不是基于弹簧的测试类。你应该有 @ContextConfiguration@RunWith(SpringJUnit4Runner.class) 让 spring 做事。
  • 还有一个事实是控制器或其他无关紧要的东西(至少对于单元测试而言)。
  • 是的,你是对的。我弄乱了样本。让我修复它。关于控制器与服务 - 如果您检查答案,建议的修复是使用 MockMvcBuilders 构建用于调用控制器的 MockMvc。这不适用于我的情况。
  • @M.Deinum 现在怎么样?
  • 如前所述,我提到的是单元测试而不是集成/系统测试!但随后我们讨论的是定义,而不是您的问题。

标签: java spring testing mockito aop


【解决方案1】:

实际上它按预期工作,但是您正在运行基于代理的 AOP 的副作用,尤其是在这种情况下基于类的代理。

目前您在代理上设置字段,而不是在代理内的实际对象上。这才是你真正想要的。要获取实际实例,请使用AopTestUtils.getUltimateTargetObject,然后在ReflectionTestUtils.setField 方法中使用它。

@Autowired
@InjectMocks
private MyService service;

@Mock
private MyServiceDependency serviceDep;

@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    MyService serviceToInject = AopTestUtils.getUltimateTargetObject(service);
    ReflectionTestUtils.setField(serviceToInject, "serviceDep", serviceDep);
}

但是我认为这种方法是错误的,当您开始像这样搞乱时,会有更好的方法。只需使用 Spring 注入模拟。为此测试用例创建一个特定的@Configuration 类。将其设为内部 public static class 并为依赖项添加模拟的 @Bean

@Configuration
@Import(BeanConfig.class) 
public static class TestBeanConfig {

    @Bean
    public MyServiceDependency myServiceDependency() {
        return Mockito.mock(MyServiceDependency.class);
    }
}

现在在您的测试类中,您可以简单地 @Autowire 两个 bean,而无需使用反射或任何其他设置依赖项。

@RunWith(SpringJUnit4ClassRunner.class)
public class MyServiceTest {

    @Autowired
    private MyService service;

    @Autowired
    private MyServiceDependency serviceDep;

    @Test
    public void test() {
        when(serviceDep.process()).thenAnswer(new Answer<Object>() {

                @Override
                public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                    throw new Exception("Sample message.");
                }

            });

        service.execute();
    }
}

这将处理正确的依赖关系。

【讨论】:

  • 我应该在MyServiceTest 添加@ContextConfiguration(classes = {TestBeanConfig.class}) 吗?
  • 没有。它将被自动检测到......当然,如果你是在内部 bean 中制作的。
  • 我喜欢你的方法——它更干净。但是方面仍然没有被触发:/
  • 确保没有 2 个依赖项实例并且未使用您的模拟。还要检查您要测试的 bean 实际上是一个代理。
  • 这个 bean 是一个代理 - 我用 AopUtils.isAopProxy 测试它。没有两个实例 - 我通过运行测试并检查是否没有“找到 2 个实例”错误或类似的东西来验证。我故意添加了另一个并且发生了错误。我认为这根本不适用于 Mockito:/
【解决方案2】:

我遇到了与@nyxz 相同的问题,这是故意的,请参阅https://github.com/spring-projects/spring-boot/issues/7243

灵感来自@M。 Deinum 以下解决方案适用于 Spring Boot 2.3.4.RELEASE 和 JUnit 5。 我们将只提供一个没有@MockedBean的模拟bean

@SpringBootTest
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class MyServiceTest {

    @Autowired
    private MyService service;

    @Test
    public void test() {
        service.execute();
    }

    static class TestBeanConfig {

         @Bean
         @Primary
         public MyServiceDependency myServiceDependency() {
             MyServiceDependency myServiceDependency = Mockito.mock(MyServiceDependency.class)
             // Add behavior of mocked bean here
             return myServiceDependency;
         }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多