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
Related
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.
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.
I have a Spring Boot 1.4.2 application. Some code which is used during startup looks like this:
#Component
class SystemTypeDetector{
public enum SystemType{ TYPE_A, TYPE_B, TYPE_C }
public SystemType getSystemType(){ return ... }
}
#Component
public class SomeOtherComponent{
#Autowired
private SystemTypeDetector systemTypeDetector;
#PostConstruct
public void startup(){
switch(systemTypeDetector.getSystemType()){ // <-- NPE here in test
case TYPE_A: ...
case TYPE_B: ...
case TYPE_C: ...
}
}
}
There is a component which determines the system type. This component is used during startup from other components. In production everything works fine.
Now I want to add some integration tests using Spring 1.4's #MockBean.
The test looks like this:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyWebApplication.class, webEnvironment = RANDOM_PORT)
public class IntegrationTestNrOne {
#MockBean
private SystemTypeDetector systemTypeDetectorMock;
#Before
public void initMock(){
Mockito.when(systemTypeDetectorMock.getSystemType()).thenReturn(TYPE_C);
}
#Test
public void testNrOne(){
// ...
}
}
Basically the mocking works fine. My systemTypeDetectorMock is used and if I call getSystemType -> TYPE_C is returned.
The problem is that the application doesn't start. Currently springs working order seems to be:
create all Mocks (without configuration all methods return null)
start application
call #Before-methods (where the mocks would be configured)
start test
My problem is that the application starts with an uninitialized mock. So the call to getSystemType() returns null.
My question is: How can I configure the mocks before application startup?
Edit: If somebody has the same problem, one workaround is to use #MockBean(answer = CALLS_REAL_METHODS). This calls the real component and in my case the system starts up. After startup I can change the mock behavior.
In this case you need to configure mocks in a way we used to do it before #MockBean was introduced - by specifying manually a #Primary bean that will replace the original one in the context.
#SpringBootTest
class DemoApplicationTests {
#TestConfiguration
public static class TestConfig {
#Bean
#Primary
public SystemTypeDetector mockSystemTypeDetector() {
SystemTypeDetector std = mock(SystemTypeDetector.class);
when(std.getSystemType()).thenReturn(TYPE_C);
return std;
}
}
#Autowired
private SystemTypeDetector systemTypeDetector;
#Test
void contextLoads() {
assertThat(systemTypeDetector.getSystemType()).isEqualTo(TYPE_C);
}
}
Since #TestConfiguration class is a static inner class it will be picked automatically only by this test. Complete mock behaviour that you would put into #Before has to be moved to method that initialises a bean.
I was able to fix it like this
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyWebApplication.class, webEnvironment = RANDOM_PORT)
public class IntegrationTestNrOne {
// this inner class must be static!
#TestConfiguration
public static class EarlyConfiguration {
#MockBean
private SystemTypeDetector systemTypeDetectorMock;
#PostConstruct
public void initMock(){
Mockito.when(systemTypeDetectorMock.getSystemType()).thenReturn(TYPE_C);
}
}
// here we can inject the bean created by EarlyConfiguration
#Autowired
private SystemTypeDetector systemTypeDetectorMock;
#Autowired
private SomeOtherComponent someOtherComponent;
#Test
public void testNrOne(){
someOtherComponent.doStuff();
}
}
You can use the following trick:
#Configuration
public class Config {
#Bean
public BeanA beanA() {
return new BeanA();
}
#Bean
public BeanB beanB() {
return new BeanB(beanA());
}
}
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {TestConfig.class, Config.class})
public class ConfigTest {
#Configuration
static class TestConfig {
#MockBean
BeanA beanA;
#PostConstruct
void setUp() {
when(beanA.someMethod()).thenReturn(...);
}
}
}
At least it's working for spring-boot-2.1.9.RELEASE
Spring's initialization is triggered before #Before Mockito's annotation so the mock is not initialized at the time the #PostConstruct annotated method is executed.
Try to 'delay' your system detection using #Lazy annotation on the SystemTypeDetector component. Use your SystemTypeDetector where you need it, keep in mind that you cannot trigger this detection in a #PostConstruct or equivalent hook.
I think that it's due to the way you autowire your dependencies. Take a look at this (specially the part about 'Fix #1: Solve your design and make your dependencies visible'). That way you can also avoid using the #PostConstruct and just use the constructor instead.
What U are using, is good for a unit tests:
org.mockito.Mockito#when()
Try to use the following methods for mocking spring beans when the context is spined-up:
org.mockito.BDDMockito#given()
If u are using #SpyBean, then u should use another syntax:
willReturn(Arrays.asList(val1, val2))
.given(service).getEntities(any());
I have this test:
#RunWith(MockitoJUnitRunner.class)
public class myServiceTest {
#InjectMocks
myService subject;
private myService spy;
#Before
public void before() {
spy = spy(subject);
}
#Test
public void testing() {
when(spy.print2()).thenThrow(new RuntimeException()).thenThrow(new RuntimeException()).thenReturn("completed");
spy.print1();
verify(spy, times(3)).print2();
}
and then I have:
#Service("myService")
public class myService extends myAbstractServiceClass {
public String print1() {
String temp = "";
temp = print2();
return temp;
}
#Retryable
public String print2() {
return "completed";
}
}
then I have this interface(which my abstractService implements):
public interface myServiceInterface {
#Retryable(maxAttempts = 3)
String print1() throws RuntimeException;
#Retryable(maxAttempts = 3)
String print2() throws RuntimeException;
}
but, I get a runtimeexception thrown when I run the test, leading me to believe it is not retrying. Am I doing this wrong?
This is because you are not using the SpringJUnitClassRunner.
Mockito and your own classes are not taking the #Retryable annotation in account. So you rely on the implementation of Spring to do so. But your test does not activate Spring.
This is from the SpringJUnit4ClassRunner JavaDoc:
SpringJUnit4ClassRunner is a custom extension of JUnit's BlockJUnit4ClassRunner which provides functionality of the Spring TestContext Framework to standard JUnit tests by means of the TestContextManager and associated support classes and annotations.
To use this class, simply annotate a JUnit 4 based test class with #RunWith(SpringJUnit4ClassRunner.class) or #RunWith(SpringRunner.class).
You should restructure your test class at least to something like:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=MyConfig.class)
public class MyServiceTest {
#Configuration
#EnableRetry
#Import(myService.class)
public static class MyConfig {}
...
What am I doing there?
activate the Spring JUnit hook
specify the Spring context configuration class
define the spring configuration and import your service as a bean
enable the retryable annotation
Are there some other pitfalls?
Yes, you are using Mockito to simulate an exception. If you want to test this behaviour with Spring like this, you should have a look at Springockito Annotations.
But be aware of that: Springockito you will replace the spring bean completely which forces you to proxy the call of your retryable. You need a structure like: test -> retryableService -> exceptionThrowingBean. Then you can use Springockito or what ever you like e.g. ReflectionTestUtils to configure the exceptionThrowingBean with the behaviour you like.
You should reference the interface type of your service in your test: MyServiceInterface
And last but not least. There is a naming convention nearly all Java developers follow: class names have first letter of each internal word capitalized
Hope that helps.
Another way:
#EnableRetry
#RunWith(SpringRunner.class)
#SpringBootTest(classes={ServiceToTest.class})
public class RetryableTest {
#Autowired
private ServiceToTest serviceToTest;
#MockBean
private ComponentInsideTestClass componentInsideTestClass;
#Test
public void retryableTest(){
serviceToTest.method();
}
}
I think you should let Spring manage the bean, create the appropriate proxy and handle the process.
If you want to mock specific beans, you can create mocks and inject them to the service under test.
1st option could be unwrapping proxied service, creating mocks and manually injecting them:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {RetryConfiguration.class})
#DirtiesContext
public class TheServiceImplTest {
#Autowired
private TheService theService;
#Before
public void setUp(){
TheService serviceWithoutProxy = AopTestUtils.getUltimateTargetObject(theService);
RetryProperties mockRetryProperties = Mockito.mock(RetryProperties.class);
ReflectionTestUtils.setField(serviceWithoutProxy, "retryProperties", mockRetryProperties);
}
#Test
public void shouldFetch() {
Assert.assertNotNull(theService);
}
}
In this example, I mocked one bean, RetryProperties, and injected into the service. Also note that, in this approach you are modifying the test application context which is cached by Spring. This means that if you don't use #DirtiesContext, service will continue its way with mocked bean in other tests. You can read more here
Second option would be creating a test specific #Configuration and mock the depended bean there. Spring will pick up this new mocked bean instead of the original one:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {RetryConfiguration.class, TheServiceImplSecondTest.TestConfiguration.class})
public class TheServiceImplSecondTest {
#Autowired
private TheService theService;
#Test
public void shouldFetch() {
Assert.assertNotNull(theService);
}
#Configuration
static class TestConfiguration {
#Bean
public RetryProperties retryProperties() {
return Mockito.mock(RetryProperties.class);
}
}
}
In this example, we have defined a test specific configuration and added it to the #ContextConfiguration.
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.