模拟Spring bean的方法行为会破坏方面

时间:2016-02-04 14:33:45

标签: java spring testing mockito aop

我搜索了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 {}

编辑:该功能有效,但我想通过测试验证它。

2 个答案:

答案 0 :(得分: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();
    }
}

将负责正确的依赖关系。

答案 1 :(得分:0)

我遇到了与@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;
         }
    }
}