Problems autowiring beans to TestNG test when using spring profiles - java

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.

Related

How can I inject property source of a bean in test

I am writing unit tests for my services in Spring, Java. I mock all dependencies in a tested class and I instantiate the tested class in a constructor, to which I pass mocked classes. The problem is that the tested class injects properties from .properties file into fields that are inside it (let's say Strings).
I use the standard combination of #PropertySource on a class level and #Value on a field level inside my tested class.
As we know, properties injection fails when class is instantiated through constructor (not as a bean during the Spring Container initialization). How do you deal with such problem?
I've got one solution, though I think it is bad and unsatisfactory, that is:
1. to #Autowire the class under test normally, then replace all its dependencies by using a setter.
I also know about the #TestPropertySource annotation and if I understand correctly, it does not provide a solution and it is only a way to override already existent properties - which is not the case, as we cannot really use any properties.
Thanks for help in advance :)
It is rather straight : in your unit test, inject the property in a String field and create the object under test not in the constructor of the test class but in the hook method invoked after the container has loaded the Spring context.
In JUnit 4, you specify this hook method with #Before and in JUnit 5 with #BeforeEach.
It would give something like :
#RunWith(SpringJUnit4ClassRunner.class)
public class FooTest{
Foo foo;
#Value("${myProp}")
String myProp;
#BeforeEach
public void beforeEach(){
foo = new Foo(myProp);
}
}
Note that to make your test be executed faster you should load from the Spring context only what your test requires : the environment part.

How to tell spring to only load the needed beans for the JUnit test?

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.

Spring profile not properly applied to tests involving #Configurable

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

Mocked Objects in Context during tests execution

I'm working on an application which has both component and integration tests. The differences between then is: a component test test more than one class (i.e., its inner objects aren't all mocked out - but some of them might be [such as JMS publishers]) and an Integration test is a test that nothing at all is mocked out. In another words, Spring gives you the object and you test it as it is.
So far, so good.
The problem is: in order to be able to replace one dependency or another from the Spring context, I used Springockito (https://bitbucket.org/kubek2k/springockito/wiki/Home) which offers you a way to mock out some bean from the Spring context.
So - in the component tests - I have this:
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext(classMode = AFTER_CLASS)
#ContextConfiguration(loader = SpringockitoContextLoader.class, locations = "classpath:spring-classify-test.xml")
public class....
#Autowired
#ReplaceWithMock
private SomeServiceInterface someServiceInterface;
#Autowired
private Bean bean;
Bean has SomeServiceInterface as a depedency.
public class Bean {
private SomeServiceInterface...
In the case above, SomeServiceInterface will be replaced by a mock. Of course, that example is an oversimplification of the problem - I replace bean with mock objects that are dependecies further down in the object graph.
It's worthy noticing that I load the context from this file: spring-classify-test.xml Also it's wothy noticing that I mark the context as dirty after the execution of the class - so, AFAIK, the next test class must reload the context.
Now the integration test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = SpringockitoContextLoader.class, locations = {"classpath:/spring-service-integration-test.xml" })
public class ...
#Autowired
private Bean bean;
I load the context from spring-service-integration-test.xml - but SomeServiceInterface inside of Bean is still mocked! The context used in the integration test was changed as well!
If I mark the Integration test with #DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD), the first test in the class will fail because SomeServiceInterface is mocked - but the next test will pass because the context has been refreshed already.
Funny thing is:
If I ask Spring to inject SomeServiceInterface in the Integration Test, it will inject a SomeServiceInterface concrete implementation - not a mock!
I have tried many things to sort out that issue:
Programatically override the beans in the context after the component tests are done using the registerBeanDefinition method from the context
Create a TestExecution listener so I could try to manually refresh the context before the execution of an IntegrationTest
Use the same loader for the different contexts....
This story goes on and on.
Does anyone have any idea?
P.S.: I quite understand that adopting Springockito was a dubious idea - but that decision was not made by me and now we have over 500 tests in the project - hence refactoring them all to remove Springockito will be a lenghty task, therefore it is not a viable option ATM.
The #DirtiesContext annotation is handled by the DirtiesContextTestExecutionListener when it's registered with the TestContextManager. With plain vanilla Spring tests, that listener is registered by default. Perhaps Springockito or something else in your test "component test" is doing something that interferes with the default listener registration?
I suggest to try latest springockito-annotations 1.0.8 with
#DirtiesMocks(classMode = DirtiesMocks.ClassMode.AFTER_CLASS)
See https://bitbucket.org/kubek2k/springockito/wiki/springockito-annotations#!reseting-mocksspies-in-experimental-package-hope-youll-like-it.

Java Spring ApplicationContext Configuration

I'm in the process of moving all of my Spring Configurations to Java code. I've run into a problem where I now want to set which profile I am using based on a command line switch or maven profile, etc... I also want to avoid having to place all of the same annotations on each of my test classes. This is not a web application, but rather a functional test suite.
Here is my attempt:
public class CompanyApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(final ConfigurableApplicationContext applicationContext) {
final AnnotationConfigApplicationContext rootContext = new AnnotationConfigApplicationContext();
rootContext.getEnvironment().setActiveProfiles(System.getProperty("spring.profile.active", "local"));
rootContext.register(LocalConfiguration.class, SauceLabsConfiguration.class);
}
}
Then I have my tests annotated with the following:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = CompanyApplicationContextInitializer.class)
However when I attempt to run my tests, my autowired pieces are not being located. Am I on the right track at all? How can I wire in this class to programatically set my ApplicationContext?
The problem with your example above is that you're passing an ApplicationContextInitializer class #ContextConfiguration#classes. The #classes attribute is intended to accept classes marked with Spring's #Configuration annotation.
ApplicationContextInitializer is intended for use primarily in web applications, where it is difficult to get programmatic access to the WebApplicationContext. The "contextInitializerClasses" init-param can be passed to the Spring DispatcherServlet, and Spring will call your ACI implementation at the right time, allowing you to manipulate the application context prior to #refresh().
In your case, it appears you are concerned only with activating profiles for an integration test. So your ACI is unnecessary. Mark your integration test with Spring's #ActiveProfiles annotation to dictate which profiles are active.
Note that if spring.profiles.active has been set as a JVM system property or environment variable, the specified profile(s) will be activated automatically. i.e. there is no need to call System#getProperty as you do in your ACI implementation. One thing to note, however, is that based on the logic in your ACI implementation, it appears you want to fall back to a profile named "local" if spring.profiles.active is note supplied as a system property or environment variable. You may be interested to know that there is a "reserved default profile" named literally "default". This probably has the same semantics you're looking for with your "local" profile. Consider renaming your 'local' profile to 'default'.
Finally, note that there does exist an open improvement request for providing ApplicationContextInitializer support in #ContextConfiguration classes: https://jira.springsource.org/browse/SPR-9011. You might want to put a watch on that. It would, for example, allow you a simple option for programmatically activating 'local' if no other profiles are active.
Try adding the locations of your app context XML to the second annotation:
#ContextConfiguration(locations = {
"classpath:applicationContext.xml"
})

Categories