12

I am using Spring 4.16 and i have my ValidationAspect, which validates methods arguments and throws ValidationException if is something wrong. This is being called when i run the server and send requests, but not when comes from the test:

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
    }
}

The config file where i create aspect the 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());
    }
}

So this is the test, it should throw ValidationException just before it gets into addSoftware method, since is an invalid softwareObject.

@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
    }
}

If i run the service and i add this invalid user from a post request, this throws ValidationException as it should be. But for some reason, it is never executing ValidationAspect method from the test layer

And my service

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)
         // ...
    }
}

I dont understand why aspect is not being called, since mockedSoftwareService bean is located in feature package and the bean name ends with "Service", so it satisfies both conditions. Do you have any idea about what could be happening ? Thanks in advance


EDIT

@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

}
jscherman
  • 5,839
  • 14
  • 46
  • 88
  • Are your aspect class defined into your test config xml? Please, provide your test config xml. (requested by @Rodrigo Gomes) – Barett Jun 08 '15 at 22:43
  • Try adding `@RunWith(SpringJUnit4ClassRunner.class)` as the Junit isn't attached to spring context yet :) – Bond - Java Bond Jun 09 '15 at 07:23
  • @Barett I haven't got any xml config. Its all configured by java annotations. The aspect configuration is just what i put up there. Thanks for your answer! – jscherman Jun 09 '15 at 14:04
  • @Bond-JavaBond I put that RunWith line and I get "IllegalState exception: Neither GenericXmlContextLoader nor AnnotationConfigContextLoader was able to detect defaults, and no ApplicationContextInitializers were declared for context configuration [ContextConfigurationAttribues]....." – jscherman Jun 09 '15 at 14:06
  • @Bond-JavaBond I've already try putting that ComponentScan in external Configuration and passing that configuration to the ContextConfiguration, and i don't get any exception but it is not working the aspect neither – jscherman Jun 09 '15 at 14:27
  • 1
    What happens if you define your bean in the test context and inject it into the test case? I think the problem is an unmanaged bean. – chrylis -cautiouslyoptimistic- Jun 10 '15 at 23:43

4 Answers4

9

Not sure what you are trying to do but your @ContextConfiguration is useless as you aren't using Spring Test to run your test (that would require a @RunWith or one of the super classes from Spring Test).

Next you are adding a singleton which is already fully mocked and configured (that is what the context assumes). I strongly suggest to use Spring instead of working around it.

First create a configuration inside your test class for testing, this configuration should do the scanning and register the mocked bean. Second use Spring Test to run your test.

@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;
        }

    }
}
M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • Thanks for your response. I am getting NoSuchBeanDefinitionException: No qualifying bean of type [ma.glasnost.orika.MapperFacade]. Do you know what it could be happening ? – jscherman Jun 15 '15 at 18:29
  • 1
    Then you haven't included the proper beans or mocks of those. – M. Deinum Jun 15 '15 at 18:36
  • What do you mean? I've put exactly your code. I don't understand what is the problem since that beans are being created... – jscherman Jun 15 '15 at 18:51
  • 1
    Do you have the correct classes mapped, don't you have a component scan somewhere that is (for some reason) being picked up? I also wonder why do you have your own aspect? Spring already has the `MethodValidationInterceptor` (although that only works with a hibernate validator). – M. Deinum Jun 15 '15 at 19:04
  • Yes, i have a component-scan, but it creates in this case just 2 classes which any of them need MapperFacade, so there is no problem with that. This mockedMapper is the only one that is being created.... Regarding to the validation aspect, i found that MethodValidationInterceptor only aplies to controllers method, and i wanted to do the validation on @Services (domain layer), that is why i found necessary to create my own aspect – jscherman Jun 15 '15 at 19:45
  • I've editted with my SoftwareServiceImpl. Maybe it helps you to see the problem – jscherman Jun 15 '15 at 19:55
  • 1
    Your understanding is wrong the `MethodValidationInterceptor` is for everything BUT controllers. Controllers have native support, other components don't have that. – M. Deinum Jun 15 '15 at 19:56
  • 1
    Rename `mapper` to `domainMapper`. Also you have `@Autowired` why are you even injecting it manually? – M. Deinum Jun 15 '15 at 19:57
  • Yes, you are right. I cant understand how i don't realize of that before. Thanks so much! Problem solved – jscherman Jun 15 '15 at 22:23
7

It is good to understand how Spring AOP works. A Spring managed bean gets wrapped in a proxy (or a few) if it is eligible for any aspect (one proxy per aspect).

Typically, Spring uses the interface to create proxies though it can do with regular classes using libraries like cglib. In case of your service that means the implementation instance Spring creates is wrapped in a proxy that handles aspect call for the method validation.

Now your test creates the SoftwareServiceImpl instance manually so it is not a Spring managed bean and hence Spring has no chance to wrap it in a proxy to be able to use the aspect you created.

You should use Spring to manage the bean to make the aspect work.

Ondrej Burkert
  • 6,617
  • 2
  • 32
  • 27
2

you need to run with srping:

@EnableAspectJAutoProxy
@RunWith(SpringJUnit4ClassRunner.class)
public class MyControllerTest {

}
peter zhang
  • 1,247
  • 1
  • 10
  • 19
0

There are indeed two important things to realize:

1) The root of the object tree has to be resolved by the registered scanned objects in the application context. If you new() it, no chance that the AOP annotations can be resolved.

2) The Annotations and AOP aspect classes need to be registered.

ad 1) @Autowire your root object will do the trick

ad 2) Make sure @Component is with the right filter: @Component() or @Component("your full namespace package filters")

Check:

    @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"));

        };
    }
Roland Roos
  • 1,003
  • 10
  • 4