I have a very weird situation, that has happened in several systems already. I am using Spring Boot and AspectJ CTW to #Autowired dependencies in some entities (instanciated outside the container).
The class that receives dependencies (an abstract entity) some times receive the dependency without applying the profile (configured by #ActiveProfile in my test class). It is not deterministic, since by changing how tests are executed different outputs can happen. To illustrate the situation with code:
The entity
#Configurable
public class AbstractMongoDocument<T> implements Persistable<T> {
#Transient
private transient MongoTemplate mongoTemplate;
//entity stuff
}
One of the failing tests
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = LOVApplication.class)
#ActiveProfiles("local-test")
public class MyCrazyIntegrationTest {
#Test
public void filterByFieldsFullMatchShouldReturnResult() throws Exception {
//Given
Location l1 = new Location("name","code",new GeoJsonPoint(11,10));
l1.save(); //Hence the need of autowiring there.
//When: whatever
//Then: Some assertions
}
}
There are some facts that I find very disturbing here:
The dependency is always injected, but some times it apparently comes from an AppCtx with the default profile.
If it fails for 1 test in 1 class, it behaves the same for all the tests in that particular class.
It may or may not happen depending on how you execute that class (At the moment it fails only if I run all the tests, but succeed if I run that class in isolation, it also behaves differently in maven).
If I debug it, I can see that the dependency injected didn't get the proper profile. (I discovered that by injecting ApplicationContext and surprisingly discovering that it was a different object than the one I received in my tests).
What worries me the most, is that now I am not sure if this situation could also happen for non-test environments with for example a Production profile, which would imply a catastrophe.
I have tried to look for open bugs in Jira and I found nothing, so I don't discard I am misconfiguring something. Any help or ideas would be very much appreciated.
The behavior you are experiencing typically should not happen in a production deployment; however, it is a known issue with integration test suites that load multiple ApplicationContexts utilizing #Configurable and AspectJ load-time weaving.
For details, see the following issues in Spring's issue tracker:
https://jira.spring.io/browse/SPR-6353
https://jira.spring.io/browse/SPR-6121
Related
I excluded part of my project for easier reproduce problem: GitHub repo.
When I compile it by Javac everything works as expected. I see logging in console when I open URLs /user/ and /user/2/:
Access: execution(List ru.krivochenko.demo.user.UserController.getAll())
Access: execution(User ru.krivochenko.demo.user.UserController.getOne(Integer))
But I wanna use AspectJ compiler. When I switch to it, error occurs:
java.lang.NoSuchMethodError: ru.krivochenko.demo.logging.LoggingAspect: method <init>()V not found
As I understood it happens because there is not no-args constructor in LoggingAspect. If I add it, I get another error, because logger is not injected:
java.lang.NullPointerException: null
at ru.krivochenko.demo.logging.LoggingAspect.beforeGettingUsers(LoggingAspect.java:28) ~[classes/:na]
So, how we can see, AspectJ ignores Autowired constructor with args.
In branch via-setter of my repo I implemented another solution. I removed #Component annotation of LoggingAspect and replaced constructor injection to setter injection. In DemoApplication.java I added #Bean configuration of LoggingAspect. It works fine, but in some situations it requires getting dependencies from application context. What is the best practice to resolve it?
Thanks for help.
Spring Aspects and compile time weaving don't automatically integrate. This is primary because aspectj and spring are fairly separate and I suspect Spring's recommended approach is not to use compile time weaving.
So thus by default Aspects are not spring magic and we need to add a little bit of plumbing to ensure they are.
In this regard, it is important to note that Aspects are not spring managed (they are managed by aspectj so we need to add something to ensure they are).
Thus the reason why you need a parameterless constructor on your aspect (so must use field injection).
Traditionally I have had to add the following piece of xml to my xml config files:
<bean id="securityAspect" class="com.<skip>.security.AuthorizationAspect"
factory-method="aspectOf" autowire="byType" />
So this works because the AspectJ compiler adds the static method aspectOf to the aspects and this method is available for acquiring the instance of the Aspect that aspectj creates (and uses).
This method is obviously not available in the source so we can't just add to our application class (DemoApplication):
#Bean
public LoggingAspect loggingAspect() {
return LoggingAspect.aspectOf();
}
Then what to do? My next option was to write some reflective code to call this method then having looked at this very helpful example that demonstrates exactly what you need - The Aspects class from AspectJ has a utilty method that does this work for us, so adding the following to our DemoApplication we have success:
#Bean
public LoggingAspect loggingAspect() {
return Aspects.aspectOf(LoggingAspect.class);
}
Btw, remove the #Component from the LoggingAspect as that will mean both Aspectj and Spring create an instance of the class...
Btw, I'd also suggest you add the following to your test class to demonstrate the problem in a test:
#Autowired
private UserController controller;
#Test
public void contextLoads() {
controller.getAll();
controller.getOne(1);
}
Btw, other suggestions to address this problem used #Configurable. I suspect this might work but you'll need to make sure you include the spring aspects java in your aspectj compile time config and I suspect it may still not work as I'm not sure the Spring context will be ready in time. i.e. if the Aspect is created before the spring context then #Configurable won't work as the beans to be injected will not yet be created.
Your approach to configure the aspect via setter injection looks valid to me. For more information about how to use AspectJ in combination with Spring check out the corresponding chapter in the Spring manual, specifically the description about how to configure AspectJ aspects by Spring IoC. It is mostly explained in the context of LTW, but it should work pretty much the same for CTW.
I am pretty new to Spring and I study using "Spring in Action" (fourth edition) by Craig Walls. The interest is not only on how to write code that is working, but also on the correct principles of using Spring.
Regarding the following piece of code from page 142, Listing 5.6:
public class HomeControllerTest {
#Test
public void testHomePage() throws Exception {
HomeController controller = new HomeController();
MockMvc mockMvc = standaloneSetup(controller).build();
mockMvc.perform(get("/")).andExpect(view().name("home"));
}
}
My questions are generated by the following thoughts:
The general understanding is that Spring uses Dependency Injection as a way to reduce the management overhead of object dependencies, increase modularity, simplify testing and code reuse. However, doesn't it imply that beans must be created and managed by the container? Since I started reading on the subject, the first detail that I memorized stated that new should never appear in a well-written piece of code that follows DI.
Could this be a solution in case we want to test a Stateful bean? I mean, if there are multiple independent tests to be run on the same instance, each of them testing the same state of the bean. Even though I found out that there is a suitable annotation for doing this (#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)).
Is there another use case that is difficult or impossible to solve otherwise (except for using new)?
A more 'to the letter' implementation would use #ContextConfiguration to specify the ApplicationContext.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = WebConfig.class)
#WebAppConfiguration
public class HomeControllerTest {
#Autowired
HomeController controller;
#Test
public void testHomePage() throws Exception {
MockMvc mockMvc = standaloneSetup(controller).build();
mockMvc.perform(get("/")).andExpect(view().name("home"));
}
}
yes, you shouldn't use new to create Spring bean instances (otherwise they're not Spring beans anymore) in production code. But the whole point of DI is to let you create and manually inject your objects with fake dependencies in unit tests. So the test code here is perfectly fine.
Yes, each unit test is free to create its own bean instance and to populate it the way it needs to. Stateful beans are extremely rare, though. They're usually stateless.
Another place where using new to create a Spring bean is precisely in #Bean-annotated methods of configuration classes. The whole point of these methods is precisely to create and initialize the Spring beans that will then be used and injected by Spring. But again, using new in unit tests is absolutely fine, and the right thing to do.
A simple question that might have an advanced answer.
The Question:
My question is, is there a way to instantiate only the classes, in your application context, needed for that specific JUnit test ?
The Reason:
My application context is getting quite big. I also do a lot of integration tests so you I guess you would understand when I say that every time I run a test all the classes in my application context get instantiated and this takes time.
The Example:
Say class Foo inject only bar
public class Foo {
#Inject
Bar bar;
#Test
public void testrunSomeMethod() throws RegisterFault {
bar.runSomeMethod();
}
but the application context has beans foobar and bar. I know this is not a vaild application context but rest assure all my code works.
<beans>
<bean id="foobar" class="some.package.FooBar"/>
<bean id="bar" class="some.package.Bar"/>
<beans>
So how do I tell spring to only instantiate Bar and ignore FooBar for the test class foo.
Thank you.
Consider adding default-lazy-init="true" to your spring context xml beans tag (or add lazy-init="true" to those specific beans that take a long time starting up).
This will ensure that only those beans are created that called with applicationContext.getBean(class-or-bean-name) or injected via #Autowired / #Inject into your tests. (Some other types of beans like #Scheduled beans will be created nevertheless but you need to check if that's a problem or not)
(if you use spring Java configuration, add #Lazy to the config files)
Caveat - If there is a bean that is not initialized explicitly with applicationContext.getBean() or injected as a dependency used by the bean obtained by using applicationContext.getBean(), then that bean will NO LONGER be constructed or initialized. Depending upon your application, that can cause things to fail OR not. Maybe you can selectively mark those beans as lazy-init="false"
Yes, we can do that, using context per test case. Prepare a test context xml file with the beans required for your test case.
If you use maven, place the test-context.xml under src/test/resources folder.
Annotate your required test class with the following annotation
#ContextConfiguration(locations = "classpath:test-application-context.xml")
This helps in loading only specific beans for the test case.
If you have two kinds of test cases, then
#Runwith(SpringJUnit4Runner.class)
#ContextConfiguration(locations = "classpath:test-context-case1.xml")
public class TestClassCase1 {}
#Runwith(SpringJUnit4Runner.class)
#ContextConfiguration(locations = "classpath:test-context-case2.xml")
public class TestClassCase2 {}
It's not direct answer, so I'd would not mark as solution. But hope it's helpful.
Generally I see three options.
As VinayVeluri answered nicely. Create separate contexts and launch them in every tests separately.
Create context one time per all tests. Just like here: Reuse spring application context across junit test classes It's a big optimization for testing all tests at once.
Mix those two first points. Create one smaller context only for testing purpose. Mock that, what's never is tested but can throw NPE etc. Like here: Injecting Mockito mocks into a Spring bean to boost up context build. And re-use it like in point 2. One time build for all tests. Personally I'd go with that one.
This one waiting for answer about some kind of smart test runner, which creates minimum needed context per test.
I am currently polishing a test framework we have. For the current needs, we must support multiple spring profiles, and run our tests multiple times, each time with a different profile. Each profile targets separate test environments, and thus different sets of tests, with different logic may be executed.
I am having a test class like this:
#ContextConfiguration(locations = { "classpath:META-INF/test-context.xml" })
public class Test extends AbstractTestNGSpringContextTests {
#Autowired
ProfileSpeciticBean profileSpecificBean;
...
}
Here, ProfileSpecificBean is an interface, that is implemented by separate classes. The actual implementation to be injected is determined by the active Spring profile, and I am using Spring XML contexts. I am building the project with Maven, using the -Dspring.profiles.active=profileName command, thus expecting the tests to catch the passed profile.
However, the current test fails with this error within the full stacktrace:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ProfileSpeciticBean found for dependency: expected at least 1 bean which qualifies as autowire candindate, found 0
After some reaearch on this topic, I found that the AbstractTestNGSpringContextTests expects an #ActiveProfiles annotation on top of the test class. So, this code works:
#ContextConfiguration(locations = { "classpath:META-INF/test-context.xml" })
#ActiveProfiles("profile1")
public class Test extends AbstractTestNGSpringContextTests ...
The problem with this is: I want to avoid hard-coding the profile name in my classes. I need to run the same test class for different profiles, by only altering the command-line script.
Is the above possible? Is there any way to make TestNG aware of the command-line profile, and re-use the same test? I need to avoid both duplicating code and configuration to make my tests run, so making two Test classes for each profile is not what I want.
Try following How to set JVM parameters for Junit Unit Tests? to set the system variables for the VM that actually runs the tests - it's not the same one as the one that runs maven.
Set your profile there.
You can use a maven system parameter to set that from the invocation of maven (or use maven profiles).
To get more precise answer I suggest you add stacktrace and the piece of you main configuration (where you declared beans that is supposed to be replaced by test beans).
Here is the general idea:
let's say you want to change PropertyPlaceholderConfigurer depending on your profile.
Steps:
You create you main-config.xml
that contains PropertyPlaceholderConfigurer and mark it with profile="default"
You create you test-config.xml with test implementation of PropertyPlaceholderConfigurer
(don't forget to mark test-config.xml with profile="MyTestProfile" or mark only PropertyPlaceholderConfigurer with profile="MyTestProfile)
Than you import both test-config.xml and main-config.xml to your tests
#ContextConfiguration(locations = { "classpath:META-INF/main-config.xml","classpath:META-INF/test-config.xml" })
#ActiveProfiles("MyTestProfile")
public class Test extends AbstractTestNGSpringContextTests {}
It should work. Good luck.
I'm working on a project that uses the Java (not xml) flavour of Spring configuration for wiring up dependencies. It also has profiling logic that should be weaved via AspectJ onto the desired methods (via annotations). The setup is working and I can see classes from my desired package being weaved and profiling information being logged out of them.
The problem is that weaving does not work for #Bean classes. I've enabled debug in aop.xml via:
<weaver options="-XnoInline -Xreweavable -verbose -debug -showWeaveInfo">
And I can see classes in my desired package being weaved, but not the beans in the configuration. If I instantiate the classes directly (not inject them) weaving works.
Unfortunately, I can't post real code here, but here's a dumbed down example:
#Configuration
#EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.ENABLED)
public class MySpringConfig {
#Bean
AnnotatedClass1 annotatedClass1() {
return new AnnotatedClass1(new AnnotatedClass2());
}
}
AnnotatedClass1 and AnnotatedClass2 live in the same package and weaving works on the one instantiated directly and not the one returned by the bean.
I've searched through the Spring AOP docs but I can't seem to find anything related to this. There is some magic you need to do for auto-proxying and some limitations for SpringAOP but load time weaving should just work as far as I can tell - I've tried on private methods for example and it worked.
The problem was the return type - if I do:
#Bean
Object annotatedClass1() {
return new AnnotatedClass1(new AnnotatedClass2());
}
the weaving starts to work for the bean as well. My initial assumption was that it has something to do with Spring caching the bean and not using the weaved version, but this didn’t make sense because:
load time weaving is supposed to act at, well… class load time :). Then, it doesn’t matter what the method returns, the class should have the aspects.
I’ve actually checked the debug output for both Spring and AspectJ and no mention of my class so it must have been ignored somehow.
This is the first time I’ve used this stuff, so I might be misunderstanding things. If anyone can explain why the return type of the #Bean method has anything to do with weaving I’d be happy to accept your answer instead of this one.