Writing an integration test for aspect functionality in Spring - java

I was wondering what I am doing wrong when testing my aspect functionality. The aspect is working in production (passed testing by QA), but I am trying to get my integration unit test to pass. Here is my code:
#Aspect
#Component
public class MyAspect {
#Pointcut("execution(* com.example.dao.UsersDao(..)) && args(.., restrictions)")
protected void allUsersPointcut(List<String> restrictions) {
}
#Around("allUsersPointcut(restrictions)")
public Object applyUserRestrictions(final ProceedingJoinPoint pjp, List<String> restrictions) throws Throwable {
String restrict = "Jack";
restrictions.add(restrict);
return pjp.proceed();
}
My DAO method just returns a list of all users, but when the aspect is used it restricts what users are shown.
#Repository
UsersDaoImpl implements UsersDao {
...
}
And my UsersService:
#Service
public class UsersService implements UsersService {
#Autowired
protected UsersDAO usersDAO;
...
#Transactional(readOnly=true)
public List<String> findUsers(List<String> restrictions) {
return this.usersDAO.findUsers(restrictions);
}
}
In my unit test I am doing the following:
#RunWith(SpringJUnit4ClassRunner.class)
public class UserTest {
#Autowired
UsersService usersService;
#Test
public void testAspect() {
List<String> restrictions = null;
List<String> users = this.usersService.findUsers(restrictions);
Assert.notNull(users);
}
I've also added the xml confguration:
context:annotation-config></context:annotation-config>
<aop:aspectj-autoproxy proxy-target-class="true"/>
<context:component-scan base-package="com.example.aspect" />
Can anyone advise what I am doing wrong?

From what I can see of your test, it ought to work - so you have some tweaking to do regarding classpath scanning, ensuring the test is using the expected configuration etc.
I recommend temporarily adding:
#Autowired
ApplicationContext context;
#Before
public void dumpBeans() {
System.out.println(context.getBeansOfType(UsersDao.class));
}
Or, more simply, System.out.println(usersDao.getClass()) in the test method.
You could also run your test in a debugger - add a breakpoint in your test class, and check what class usersDao is at runtime.

Related

How to write a test for a service in which beans self-register

Problem with correct class setting for tests.
I have the following service structure
My service:
Interface
public interface ColumnsFromTableService {
List<ColumnsDto> getTableColumnsFromSource(DataProvider dataProvider, String tableName);
DataProviderSourceType myDataProviderSourceType();
#Autowired
default void regMe(ColumnsFromTableFacade columnsFromTableFacade){
columnsFromTableFacade.register(myDataProviderSourceType(),this);
}
}
Impl
#Service
#RequiredArgsConstructor
public class OracleColumnsFromTableServiceImpl implements ColumnsFromTableService {
private final DataProviderInsideDao dataProviderInsideDao;
#Override
public List<ColumnsDto> getTableColumnsFromSource(DataProvider dataProvider, String tableName) {
return dataProviderInsideDao.getColumnsByTableNameFromOracle(dataProvider, tableName);
}
#Override
public DataProviderSourceType myDataProviderSourceType() {
return DataProviderSourceType.ORACLE;
}
}
My facade:
Interface
public interface ColumnsFromTableFacade {
List<ColumnsDto> getTableColumnsFromSource(DataProvider dataProvider, String tableName);
void register(DataProviderSourceType dataProviderSourceType, ColumnsFromTableService columnsDataProviderService);
}
Impl
#Service
public class ColumnsFromTableFacadeImpl implements ColumnsFromTableFacade {
private final Map<DataProviderSourceType, ColumnsFromTableService> implementationMap = new HashMap<>();
#Override
public List<ColumnsDto> getTableColumnsFromSource(DataProvider dataProvider, String tableName) {
ColumnsFromTableService columnsFromTableService = implementationMap.get(dataProvider.getSourceType());
return columnsFromTableService.getTableColumnsFromSource(dataProvider,tableName);
}
#Override
public void register(DataProviderSourceType dataProviderSourceType, ColumnsFromTableService columnsDataProviderService) {
implementationMap.put(dataProviderSourceType, columnsDataProviderService);
}
}
For use, I inject the facade in the place I need.
Everything works in the application. When creating ColumnsFromTableService beans, Spring Boot sees the #Autowired annotation in the interface and and registers the service in the facade. But when testing this facade, I can't set it up correctly.
My test:
#ExtendWith(MockitoExtension.class)
public class EasyServiceTest {
#InjectMocks
TablesFromSourceFacadeImpl tablesFromSourceFacade;
#Test
void test(){
tablesFromSourceFacade.getAllTablesFromSource(new DataProvider());
}
}
When running the test, the facade is successfully instantiated. But the collection with implementations is empty.
enter image description here
I am using
Junit jupiter - 5.7.1
Spring boot - 2.4.3
I decided to be rough
#ExtendWith(MockitoExtension.class)
public class EasyServiceTest {
TablesFromSourceFacadeImpl tablesFromSourceFacade;
#InjectMocks
OracleTablesFromSourceServiceImpl oracleTablesFromSourceService;
#InjectMocks
OracleColumnsFromTableServiceImpl oracleColumnsFromTableService;
#BeforeEach
void setUp() {
tablesFromSourceFacade = new TablesFromSourceFacadeImpl();
tablesFromSourceFacade.register(postgresTablesFromSourceService.myDataProviderSourceType(),
postgresTablesFromSourceService);
tablesFromSourceFacade.register(oracleTablesFromSourceService.myDataProviderSourceType(),
oracleTablesFromSourceService);
}
#Test
void test(){
tablesFromSourceFacade.getAllTablesFromSource(new DataProvider());
}
}
UPDATED
The second solution to the problem is to raise either the entire context of the spring, or part of it. But in my case, this did not work, since the services are scattered across different packages, and I would have to raise the entire context, which is heavy.

How to avoid spring AOP aspect being called during test

I need to avoid an aspect being called when unit testing a class.
I'm working with Java 8, spring 4.3.22.RELEASE and mockito. I have a #Service and a unit test for it. I also have an #Aspect that defines a pointcut on a method in the service and it is working fine when I run my application. The problem is when I run my unit test, the aspect is called and a NullPointerException is raised because of a missing dependency in the aspect.
Service class:
#Service
public class ContactService {
#Autowired
public InContactService(ContactDao contactDao) {
this.contactDao = contactDao;
}
public boolean muteCall(Long contactId) {
return contactDao.muteCall(contactId);
}
}
Service test:
public class ContactServiceTest {
#Mock
private ContactDao contactDao;
private ContactService contactService;
#Before
public void setUp(){
MockitoAnnotations.initMocks(this);
contactService = new ContactService(contactDao);
}
#Test
public void testMuteCall(){
contactService.muteCall(1L);
}
}
Aspect:
#Aspect
public class ContactAspect {
private MeterRegistry registry;
public void setRegistry(MeterRegistry registry) {
this.registry = registry;
}
#AfterReturning(pointcut = "execution(* com.company.ContactService.muteCall(..))", returning = "retVal")
public void checkReturnContactServiceMuteCall(JoinPoint joinPoint, boolean retVal) {
Object[] args = joinPoint.getArgs();
registry.counter("my.metric.mute_call").increment();
}
}
Application context:
#Configuration
public class ApplicationContext {
#Bean
public MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
}
#Bean
public ContactAspect contactAspect() {
ContactAspect aspect = Aspects.aspectOf(ContactAspect.class);
aspect.setRegistry(meterRegistry());
return aspect;
}
}
I expected that when the test is ran the aspect is not called. Currently I get a NullPointerException when I run the test because registry is not defined in the aspect.
The best approach is using Spring profiles, which allows you to have different running schemes.
check this:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html
I ran into this issue with legacy code to which I wanted to add integration tests but didn't need or want the aspects to be invoked.
There most likely is somewhere in your context configuration telling the application to enable aspects. Wherever that is, find it, and disable it.
In my case, the configs were XML based so in my applicationContext-services-integration-test.xml file being loaded for my integration tests, I commented out
<aop:aspectj-autoproxy /> and it bypassed all the aspects for my tests.
Cheers!
We've run into the same problem and fixed it by disabling property when running tests.
import org.aspectj.lang.annotation.Aspect;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
#Aspect
#ConditionalOnExpression("${aspect.property.enabled:true}")
public class AspectClass {
test/resources/application.properties
aspect.property.enabled=false

Excluding an ApplicationListener #Component in Spring Boot during tests

I am trying to have my test unit up and running, and I have encountered a weird issue. My application uses an ApplicationListener class annotated as a #Component to perform an operation during startup.
During tests I have mocked the service that contains the logic, but I found that even though Mockito's when instructions work well in controller scope, the bean is not initialized for this ApplicationListener class: instead of returning what I define in the test unit, it returns either false or null - depending on the data type returned by each method in the service.
Since I have not found any way to initialize the mocked service from the test unit for the ApplicationListener class, I have decided to exclude it. To do so I have tried different approaches, being the one most often used that of creating a test application context and change its configuration. Unfortunately, nothing I have seen is working - so I am here asking for help. If possible, I would prefer not touching the ApplicationListener class and do all related coding in the test code.
I am interested in any of the two possible solutions, if they can be done:
1.- Get the mocked behaviour during the ApplicationListener execution, but I have read somewhere that this cannot be done
2.- Exclude the #Component from the test unit somehow.
TestUnit.Java:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class TestConfigurationService {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
private MockService mockService;
private void initMockBean () throws Exception {
when(mockService.isDoingSomething()).thenReturn(true);
}
#Before
public void setup() throws Exception {
// Spring mock context application setup
this.mockMvc = webAppContextSetup(webApplicationContext).build();
// Initialize ConsulService mock bean
initMockBean ();
}
}
TestApplication.java
#SpringBootApplication
#EnableAutoConfiguration
#ComponentScan(basePackages="my.base.package", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = StartupConfiguration.class))
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
Besides what is shown in the code, I have also tried this annotation in file TestApplication.java:
#SpringBootApplication(exclude={StartupConfiguration.class})
StartupConfiguration.java
#Component
public class StartupConfiguration implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private ConfigurationService configurationService;
#Override
public void onApplicationEvent(final ContextRefreshedEvent event) {
try {
configurationService.updateConfiguration();
} catch (Exception e) {
throw new RuntimeException ("Error", e);
}
}
}
ConfigurationService.java
public interface ConfigurationService {
public void updateConfiguration () throws Exception;
}
ConfigurationServiceImpl.java
#Service
#Transactional
public class ConfigurationServiceImpl implements ConfigurationService {
#Autowired
private MService mockService;
#Override
public void updateConfiguration() throws Exception {
if (mockService.isDoingSomething()==false)
throw new Exception ("Something went wrong");
}
}
Versions:
Spring Boot 1.5.4.RELEASE,
Java 1.8
You can create mock bean of the same type and mark it with #Primary annotation to replace real bean. You can achieve this by having test such configuration:
#Configuration
#Import(TestApplication.class)
public class TestConfiguration {
#Bean
#Primary
public ConfigurationService configurationService() {
return Mockito.mock(ConfigurationService.class);
}
}
then get this mock in test:
...
public class TestConfigurationService {
...
#Autowired
ConfigurationService configurationService;
#Before
public void setUp() {
when(mockService.isDoingSomething()).thenReturn(true);
}
}
Thanks, araxn1d. Your answer gave me the clue to solve this issue.
I mocked the StartupConfiguration class in TestUnit.java:
#MockBean
private StartupConfiguration startupConfiguration;
Though in this case I was lucky: application listeners don't have returning methods, so they don't need when test configuration. If I had required that some method there returned for example true or a value, this method would not apply.
But at least for application listeners, this is enough.

Mocking Spring bean's method behavior breaks aspects

I searched SO and found bunch of other questions that looked similar but not exactly, so I'll ask another one.
I have Spring application and say I created custom aspect (looking for CatchMe annotation) to log exceptions in a specific way. I want to test the aspect by mocking the behavior of one of my Spring #Service class's method so it throws exception when it is called. Then in another method, annotated with my custom annotation #CatchMe, I call the first method. What I expect to happen is the exception to get logged. Unfortunatelly the exception is thrown but the aspect is not triggered. So how can I make the aspect to get triggered in this test using Mockito?
Note: I've checked those (plus a bunch more):
Unit testing Spring #Around AOP methods
Spring Aspect not triggered in unit test
Spring: cannot inject a mock into class annotated with the #Aspect annotation
but most of them are Controller related and not Service related and I want to test only the service.
The Test
#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();
}
}
Services
#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 and Aspect
#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 {}
EDIT: The functionality works but I want to verify it with the test.
Actually it is working as expected, however you are running in a side effect of proxy based AOP, especially class based proxies in this case.
Currently you are setting the field on the proxy and not on the actual object inside the proxy. Which is what you actually want. To obtain the actual instance use AopTestUtils.getUltimateTargetObject and then use that in the ReflectionTestUtils.setField method.
#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);
}
However I think that approach is wrong, when you start messing around like this there is a better way. Simply use Spring to inject the mock. Create a specific #Configuration class for this test case. Make it a internal public static class and for the dependency add a mocked #Bean.
#Configuration
#Import(BeanConfig.class)
public static class TestBeanConfig {
#Bean
public MyServiceDependency myServiceDependency() {
return Mockito.mock(MyServiceDependency.class);
}
}
Now in your test class you can simply #Autowire both beans and not need to use reflection or whatever to set dependencies.
#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();
}
}
Which will take care of the correct dependencies.
I had the same problem as #nyxz and this is intentional, see https://github.com/spring-projects/spring-boot/issues/7243.
Inspired by #M. Deinum following solution worked for me with Spring Boot 2.3.4.RELEASE and JUnit 5.
We will just provide a mocked bean without #MockedBean
#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;
}
}
}

How to disable Spring autowiring in unit tests for #Configuration/#Bean usage

I want configure a component test using spring-test configuration inner class (#Configuration). Tested components has some services which I'd like to mock for the test. These services are classes (no interface used) and have spring annotations (#Autowired) in them. Mockito can easily mock them, however, I found no way of disabling spring autowiring.
Example how I can easily reproduce:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SomeTest.Beans.class)
public class SomeTest {
// configured in component-config.xml, using ThirdPartyService
#Autowired
private TestedBean entryPoint;
#Test
public void test() {
}
#Configuration
#ImportResource("/spring/component-config.xml")
static class Beans {
#Bean
ThirdPartyService createThirdPartyService() {
return mock(ThirdPartyService.class);
}
}
}
public class ThirdPartyService {
#Autowired
Foo bar;
}
public class TestedBean {
#Autowired
private ThirdPartyService service;
}
In this example "TestBean" represents the service to be mocked. I would NOT like "bar" to be injected by spring! #Bean(autowire = NO) does not help (in fact, that's the default value).
(Please save me from "use interfaces!" comments - the mocked service can be 3rd party which I can't do anything with.)
UPDATE
Springockito partially solves the problem, as long as you don't have to have anything else to configure (so you can't use configuration class with Springockito - it does not support it), but use mocks only.
Still looking for pure spring solution, if there's any...
Here is my solution to your problem:
import static org.mockito.Mockito.mockingDetails;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class MockitoSkipAutowireConfiguration {
#Bean MockBeanFactory mockBeanFactory() {
return new MockBeanFactory();
}
private static class MockBeanFactory extends InstantiationAwareBeanPostProcessorAdapter {
#Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return !mockingDetails(bean).isMock();
}
}
}
and then just
#Import(MockitoSkipAutowireConfiguration.class)
in your test #Configuration and you are all set
I solved it by creating FactoryBean for my bean instead of just mocking bean. At this way Spring don't try to autowire fields.
Factory bean helping class:
public class MockitoFactoryBean<T> implements FactoryBean<T> {
private final Class<T> clazz;
public MockitoFactoryBean(Class<T> clazz) {
this.clazz = clazz;
}
#Override public T getObject() throws Exception {
return mock(clazz);
}
#Override public Class<T> getObjectType() {
return clazz;
}
#Override public boolean isSingleton() {
return true;
}
}
Actual test context part:
#Configuration
public class TestContext {
#Bean
public FactoryBean<MockingService> mockingService() {
return new MockitoFactoryBean<>(MockingService.class);
}
}
Check Spring profiles. You don't need to disable auto wiring, you need to inject different beans for different configuration.
You could add the mocked service manually to the spring application context via org.springframework.beans.factory.config.SingletonBeanRegistry#registerSingleton. This way the mock is not post-processed by spring and spring does not attempt to autowire the mock. The mock itself will be injected into your tested bean.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SomeTest.Beans.class)
public class SomeTest {
// configured in component-config.xml, using ThirdPartyService
#Autowired
private TestedBean entryPoint;
#Autowired
private ThirdPartyService thirdPartyServiceMock;
#Test
public void test() {
}
#Configuration
static class Beans {
#Autowired
private GenericApplicationContext ctx;
#Bean
TestedBean testedBean() {
ctx.getBeanFactory().registerSingleton("thirdPartyService", mock(ThirdPartyService.class));
return new TestedBean();
}
}
public static class ThirdPartyService {
#Autowired
Object bar;
}
public static class TestedBean {
#Autowired
private ThirdPartyService service;
}
}
I am in quite the same situation.
What I found that if you do not set the context loader by #ContextConfiguration annotation on your test class, the default context loader will be used, which derived from AbstractGenericContextLoader. I had a look at its source and turned out it registers all the bean post processors which are responsible for reading annotations such #Autowired. In other words, annotation config is enabled by default.
So the main problem is that there are two configurations which are in conflict: in the java config we said that autowiring is not needed, while the autowired annotation tells the opposite. The real question is how to disable the annotation processing in order to eliminate the undesired configuration.
As far as I know there is no such spring implementation of ContextLoader which would not be derived from AbstractGenericContextLoader so I guess the only we can do is to write our own. It would be something like this:
public static class SimpleContextLoader implements ContextLoader {
#Override
public String[] processLocations(Class<?> type, String... locations) {
return strings;
}
#Override
public ApplicationContext loadContext(String... locations) throws Exception {
// in case of xml configuration
return new ClassPathXmlApplicationContext(strings);
// in case of java configuration (but its name is quite misleading)
// return new AnnotationConfigApplicationContext(TestConfig.class);
}
}
Of course it would be worth to spend more time to find out how to implement ContextLoader properly.
Cheers,
Robert
There are so many ways of doing this, I'm pretty sure that this answer will be incomplete, but here are a few options...
As currently seems to be recommended practice, use constructor injection for your services rather than autowiring the fields directly. This makes testing like this so much easier.
public class SomeTest {
#Mock
private ThirdPartyService mockedBean;
#Before
public void init() {
initMocks(this);
}
#Test
public void test() {
BeanUnderTest bean = new BeanUnderTest(mockedBean);
// ...
}
}
public class BeanUnderTest{
private ThirdPartyService service;
#Autowired
public BeanUnderTest(ThirdPartyService ThirdPartyService) {
this.thirdPartyService = thirdPartyService;
}
}
By doing that, you can also mix up autowired and mocked services by autowiring into the test itself and then constructing the beans under test with the most useful mix of autowired and mocked beans.
A reasonable alternative is to use Spring profiles to define stub services. This is particularly useful when wish to use the same stubbed features in multiple tests:
#Service
#Primary
#Profile("test")
public class MyServiceStub implements MyService {
// ...
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SomeTest.Beans.class)
#ActiveProfiles({"test"})
public class SomeTest {
// ...
}
By using the #Primary annotation, it ensures that this stub bean will be used instead of any other bean implementing the MyService interface. I tend to use this approach for things like email services, where by changing profile, I'm able to switch between a real mail server and Wiser.

Categories