Spring 测试中未调用方面
Aspect not being called in Spring test
我正在使用 Spring 4.16 并且我有我的 ValidationAspect,它验证方法参数并在出现问题时抛出 ValidationException。这是在我 运行 服务器和发送请求时调用的,但不是来自测试时调用的:
package com.example.movies.domain.aspect;
...
@Aspect
public class ValidationAspect {
private final Validator validator;
public ValidationAspect(final Validator validator) {
this.validator = validator;
}
@Pointcut("execution(* com.example.movies.domain.feature..*.*(..))")
private void selectAllFeatureMethods() {
}
@Pointcut("bean(*Service)")
private void selectAllServiceBeanMethods() {
}
@Before("selectAllFeatureMethods() && selectAllServiceBeanMethods()")
public synchronized void validate(JoinPoint joinPoint) {
// Validates method arguments which are annotated with @Valid
}
}
我创建 aspect bean 的配置文件
package com.example.movies.domain.config;
...
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AspectsConfiguration {
@Bean
@Description("Hibernate validator. Used to validate request's input")
public Validator validator() {
ValidatorFactory validationFactory = Validation.buildDefaultValidatorFactory();
return validationFactory.getValidator();
}
@Bean
@Description("Method validation aspect")
public ValidationAspect validationAspect() {
return new ValidationAspect(this.validator());
}
}
所以这是测试,它应该在进入 addSoftware 方法之前抛出 ValidationException,因为它是一个无效的软件对象。
@ContextConfiguration
@ComponentScan(basePackages = {"com.example.movies.domain"})
public class SoftwareServiceTests {
private static final Logger LOGGER = LoggerFactory.getLogger(SoftwareServiceTests.class.getName());
private SoftwareService softwareService;
@Mock
private SoftwareDAO dao;
@Mock
private MapperFacade mapper;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
this.softwareService = new SoftwareServiceImpl(this.dao);
((SoftwareServiceImpl) this.softwareService).setMapper(this.mapper);
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SoftwareServiceTests.class);
ctx.getBeanFactory().registerSingleton("mockedSoftwareService", this.softwareService);
this.softwareService = (SoftwareService) ctx.getBean("mockedSoftwareService");
}
@Test(expected = ValidationException.class)
public void testAddInvalidSoftware() throws ValidationException {
LOGGER.info("Testing add invalid software");
SoftwareObject softwareObject = new SoftwareObject();
softwareObject.setName(null);
softwareObject.setType(null);
this.softwareService.addSoftware(softwareObject); // Is getting inside the method without beeing validated so doesn't throws ValidationException and test fails
}
}
如果我 运行 服务并且我从 post 请求中添加了这个无效用户,这应该会抛出 ValidationException。但由于某种原因,它从不从测试层执行 ValidationAspect 方法
还有我的服务
package com.example.movies.domain.feature.software.service;
...
@Service("softwareService")
public class SoftwareServiceImpl
implements SoftwareService {
@Override
public SoftwareObject addSoftware(@Valid SoftwareObject software) {
// If gets into this method then software has to be valid (has been validated by ValidationAspect since is annotated with @Valid)
// ...
}
}
我不明白为什么不调用方面,因为mockedSoftwareService bean位于功能包中,bean名称以"Service"结尾,所以它满足这两个条件。你知道会发生什么吗?提前致谢
编辑
@Service("softwareService")
public class SoftwareServiceImpl
implements SoftwareService {
private static final Logger LOGGER = LoggerFactory.getLogger(SoftwareServiceImpl.class.getName());
private SoftwareDAO dao;
private MapperFacade mapper;
@Autowired
private SoftwareCriteriaSupport criteriaSupport;
@Autowired
private SoftwareDefaultValuesLoader defaultValuesLoader;
@Autowired
public SoftwareServiceImpl(SoftwareDAO dao) {
this.dao = dao;
}
@Autowired
@Qualifier("domainMapper")
public void setMapper(MapperFacade mapper) {
this.mapper = mapper;
}
// other methods
}
不确定您要做什么,但是您的 @ContextConfiguration
没有用,因为您没有使用 Spring 测试 运行 您的测试(这需要 @RunWith
或来自 Spring 测试的超级 classes 之一)。
接下来您要添加一个已经完全模拟和配置的单例(这是上下文假定的)。我强烈建议使用 Spring 而不是解决它。
首先在测试中创建一个配置 class 用于测试,此配置应该进行扫描并注册模拟 bean。第二次使用 Spring 测试 运行 你的测试。
@ContextConfiguration
public class SoftwareServiceTests extends AbstractJUnit4SpringContextTests {
private static final Logger LOGGER = LoggerFactory.getLogger(SoftwareServiceTests.class.getName());
@Autowired
private SoftwareService softwareService;
@Test(expected = ValidationException.class)
public void testAddInvalidSoftware() throws ValidationException {
LOGGER.info("Testing add invalid software");
SoftwareObject softwareObject = new SoftwareObject();
softwareObject.setName(null);
softwareObject.setType(null);
this.softwareService.addSoftware(softwareObject);
}
@Configuration
@Import(AspectsConfiguration.class)
public static class TestConfiguration {
@Bean
public SoftwareDAO softwareDao() {
return Mockito.mock(SoftwareDAO.class);
}
@Bean
public MapperFacade domainMapper() {
return Mockito.mock(MapperFacade.class)
}
@Bean
public SoftwareService softwareService() {
SoftwareServiceImpl service = new SoftwareServiceImpl(softwareDao())
return service;
}
}
}
了解 Spring AOP 的工作原理很有帮助。如果一个 Spring 托管 bean 符合任何方面的条件(每个方面一个代理),它就会被包装在一个(或几个)代理中。
通常,Spring 使用接口创建代理,尽管它可以使用像 cglib 这样的库来处理常规 类。对于您的服务,这意味着 Spring 创建的实现实例被包装在一个代理中,该代理处理方法验证的方面调用。
现在您的测试手动创建 SoftwareServiceImpl 实例,因此它不是 Spring 托管 bean,因此 Spring 没有机会将其包装在代理中以能够使用您创建的方面.
您应该使用 Spring 来管理 bean 以使方面工作。
确实有两件重要的事情需要意识到:
1) 对象树的根必须由应用程序上下文中注册的扫描对象解析。如果你 new() 它,就不可能解决 AOP 注释。
2) 注释和 AOP 方面 类 需要注册。
ad 1) @Autowire 你的根对象就可以了
ad 2) 确保@Component 使用正确的过滤器:
@Component() 或@Component("your full namespace package filters")
检查:
@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx)
{
return args ->
{
log.debug("Let's inspect the beans provided by Spring Boot:");
List<String> beanNames = Arrays.asList(ctx.getBeanDefinitionNames());
Assert.isTrue( beanNames.contains("yourAspectClassName"));
};
}
你需要 运行 弹簧:
@EnableAspectJAutoProxy
@RunWith(SpringJUnit4ClassRunner.class)
public class MyControllerTest {
}
我正在使用 Spring 4.16 并且我有我的 ValidationAspect,它验证方法参数并在出现问题时抛出 ValidationException。这是在我 运行 服务器和发送请求时调用的,但不是来自测试时调用的:
package com.example.movies.domain.aspect;
...
@Aspect
public class ValidationAspect {
private final Validator validator;
public ValidationAspect(final Validator validator) {
this.validator = validator;
}
@Pointcut("execution(* com.example.movies.domain.feature..*.*(..))")
private void selectAllFeatureMethods() {
}
@Pointcut("bean(*Service)")
private void selectAllServiceBeanMethods() {
}
@Before("selectAllFeatureMethods() && selectAllServiceBeanMethods()")
public synchronized void validate(JoinPoint joinPoint) {
// Validates method arguments which are annotated with @Valid
}
}
我创建 aspect bean 的配置文件
package com.example.movies.domain.config;
...
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AspectsConfiguration {
@Bean
@Description("Hibernate validator. Used to validate request's input")
public Validator validator() {
ValidatorFactory validationFactory = Validation.buildDefaultValidatorFactory();
return validationFactory.getValidator();
}
@Bean
@Description("Method validation aspect")
public ValidationAspect validationAspect() {
return new ValidationAspect(this.validator());
}
}
所以这是测试,它应该在进入 addSoftware 方法之前抛出 ValidationException,因为它是一个无效的软件对象。
@ContextConfiguration
@ComponentScan(basePackages = {"com.example.movies.domain"})
public class SoftwareServiceTests {
private static final Logger LOGGER = LoggerFactory.getLogger(SoftwareServiceTests.class.getName());
private SoftwareService softwareService;
@Mock
private SoftwareDAO dao;
@Mock
private MapperFacade mapper;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
this.softwareService = new SoftwareServiceImpl(this.dao);
((SoftwareServiceImpl) this.softwareService).setMapper(this.mapper);
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SoftwareServiceTests.class);
ctx.getBeanFactory().registerSingleton("mockedSoftwareService", this.softwareService);
this.softwareService = (SoftwareService) ctx.getBean("mockedSoftwareService");
}
@Test(expected = ValidationException.class)
public void testAddInvalidSoftware() throws ValidationException {
LOGGER.info("Testing add invalid software");
SoftwareObject softwareObject = new SoftwareObject();
softwareObject.setName(null);
softwareObject.setType(null);
this.softwareService.addSoftware(softwareObject); // Is getting inside the method without beeing validated so doesn't throws ValidationException and test fails
}
}
如果我 运行 服务并且我从 post 请求中添加了这个无效用户,这应该会抛出 ValidationException。但由于某种原因,它从不从测试层执行 ValidationAspect 方法
还有我的服务
package com.example.movies.domain.feature.software.service;
...
@Service("softwareService")
public class SoftwareServiceImpl
implements SoftwareService {
@Override
public SoftwareObject addSoftware(@Valid SoftwareObject software) {
// If gets into this method then software has to be valid (has been validated by ValidationAspect since is annotated with @Valid)
// ...
}
}
我不明白为什么不调用方面,因为mockedSoftwareService bean位于功能包中,bean名称以"Service"结尾,所以它满足这两个条件。你知道会发生什么吗?提前致谢
编辑
@Service("softwareService")
public class SoftwareServiceImpl
implements SoftwareService {
private static final Logger LOGGER = LoggerFactory.getLogger(SoftwareServiceImpl.class.getName());
private SoftwareDAO dao;
private MapperFacade mapper;
@Autowired
private SoftwareCriteriaSupport criteriaSupport;
@Autowired
private SoftwareDefaultValuesLoader defaultValuesLoader;
@Autowired
public SoftwareServiceImpl(SoftwareDAO dao) {
this.dao = dao;
}
@Autowired
@Qualifier("domainMapper")
public void setMapper(MapperFacade mapper) {
this.mapper = mapper;
}
// other methods
}
不确定您要做什么,但是您的 @ContextConfiguration
没有用,因为您没有使用 Spring 测试 运行 您的测试(这需要 @RunWith
或来自 Spring 测试的超级 classes 之一)。
接下来您要添加一个已经完全模拟和配置的单例(这是上下文假定的)。我强烈建议使用 Spring 而不是解决它。
首先在测试中创建一个配置 class 用于测试,此配置应该进行扫描并注册模拟 bean。第二次使用 Spring 测试 运行 你的测试。
@ContextConfiguration
public class SoftwareServiceTests extends AbstractJUnit4SpringContextTests {
private static final Logger LOGGER = LoggerFactory.getLogger(SoftwareServiceTests.class.getName());
@Autowired
private SoftwareService softwareService;
@Test(expected = ValidationException.class)
public void testAddInvalidSoftware() throws ValidationException {
LOGGER.info("Testing add invalid software");
SoftwareObject softwareObject = new SoftwareObject();
softwareObject.setName(null);
softwareObject.setType(null);
this.softwareService.addSoftware(softwareObject);
}
@Configuration
@Import(AspectsConfiguration.class)
public static class TestConfiguration {
@Bean
public SoftwareDAO softwareDao() {
return Mockito.mock(SoftwareDAO.class);
}
@Bean
public MapperFacade domainMapper() {
return Mockito.mock(MapperFacade.class)
}
@Bean
public SoftwareService softwareService() {
SoftwareServiceImpl service = new SoftwareServiceImpl(softwareDao())
return service;
}
}
}
了解 Spring AOP 的工作原理很有帮助。如果一个 Spring 托管 bean 符合任何方面的条件(每个方面一个代理),它就会被包装在一个(或几个)代理中。
通常,Spring 使用接口创建代理,尽管它可以使用像 cglib 这样的库来处理常规 类。对于您的服务,这意味着 Spring 创建的实现实例被包装在一个代理中,该代理处理方法验证的方面调用。
现在您的测试手动创建 SoftwareServiceImpl 实例,因此它不是 Spring 托管 bean,因此 Spring 没有机会将其包装在代理中以能够使用您创建的方面.
您应该使用 Spring 来管理 bean 以使方面工作。
确实有两件重要的事情需要意识到:
1) 对象树的根必须由应用程序上下文中注册的扫描对象解析。如果你 new() 它,就不可能解决 AOP 注释。
2) 注释和 AOP 方面 类 需要注册。
ad 1) @Autowire 你的根对象就可以了
ad 2) 确保@Component 使用正确的过滤器: @Component() 或@Component("your full namespace package filters")
检查:
@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx)
{
return args ->
{
log.debug("Let's inspect the beans provided by Spring Boot:");
List<String> beanNames = Arrays.asList(ctx.getBeanDefinitionNames());
Assert.isTrue( beanNames.contains("yourAspectClassName"));
};
}
你需要 运行 弹簧:
@EnableAspectJAutoProxy
@RunWith(SpringJUnit4ClassRunner.class)
public class MyControllerTest {
}