How to disable Autowiring in Spring JUnit Tests? - spring

Im currently working on a tool, which analyzes ApplicationContexts of Spring applications. To start the analysis I had implemented an abstract JUnit-Test class, which has to be extended by an concrete instance in the target application. This makes it very easy to write own Tests for further context-files and my tool could use the context loading functionality as well.
The problem for me is that Spring-based JUnit-Tests automatically enable Autowiring, even when it is not enabled in the target context. But I need the unchanged context, to ensure the correctness of the analysis results.
My abstract Test-Class which is embedded in the analysis tool looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
public abstract class AbstractContextLoader implements ApplicationContextAware {
private ApplicationContext applicationContext;
#Test
public void analyseContext() {
SpringContextAnalysis.startApplication(applicationContext);
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
A concrete implementation could look like this:
#ContextConfiguration(locations="context.xml")
public class ContextAnalysis extends AbstractContextLoader {
}
Is it possible to disable the automatic instantiation of Autowiring-Components while using Spring-based JUnit-Tests?

Maybe you should try storing it in an Object and casting it when needed? You'll need to do the same with the setter method's parameter.
Otherwise you could try annotating it with #Autowired(required=false), but I doubt that would work.

Related

#Autowired notation is not working as expected

#SpringBootApplication
public class MainApplication {
#Autowired
static BibliographyIndexer bi;
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
bi.reindex();
}
}
#Repository
public class BibliographyIndexer {
...
}
Whenever I access the properties of bi I get a NullPointerException. I know the #Autowired notation didn't work. But why?
Note: both classes are under the same package.
Additional question: Since I want to run a method upon the start of the spring application. Is this the best approach since #pepevalbe's answer already gave me the workaround I needed. Is there another way to run a method upon the start of the spring application?
Because you can't #Autorwire an static class. It doesn't get initialized so you get a NPE when trying to use it.
There are workarounds to wire a bean into a static class, but it is strongly discouraged.
EDIT:
If you need to execute code after initilization you could add an event listener:
#SpringBootApplication
public class MainApplication {
#Autowired
BibliographyIndexer bi;
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
#EventListener(ApplicationReadyEvent.class)
public void doAfterStartUp() {
bi.reindex();
}
}
There are several reasons #Autowired might not work.
When a new instance is created not by Spring but by for example manually calling a constructor, the instance of the class will not be registered in the Spring context and thus not available for dependency injection. Also when you use #Autowired in the class of which you created a new instance, the Spring context will not be known to it and thus most likely this will also fail.
Another reason can be that the class you want to use #Autowired in, is not picked up by the ComponentScan. This can basically be because of two reasons.
The package is outside the ComponentScan search path. Move the package to a scanned location or configure the ComponentScan to fix this.
The class in which you want to use #Autowired does not have a Spring annotation. Add one of the following annotatons to the class: #Component, #Repository, #Service, #Controller, #Configuration. They have different behaviors so choose carefully........
Your problem is that you cannot use bi in main because main is static.
Making bi static doesn't help because static fields will not be #Autowired (It is possible but does not make sense in the concepts of Dependency Injection).
Remove static and move bi.reindex() to a new method annotated with #PostConstruct. It will be executed after the MainApplication-bean is fully initialized and here you can use your injected bi.
In main method you can refer to context and from it get access to bean BibliographyIndexer. In static main spring can not creates and injects bean so this is how you can get it from context.
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(MainApplication.class, args);
BibliographyIndexer bibliographyIndexer = context.getBean(BibliographyIndexer.class);
bibliographyIndexer.reindex();
}
You can also do as in answer from pepevalbe and execute this code after initialization.

Spring static context accessor and integration tests

We have a spring component which sets the application context into a static field. This static field is then accessed from other parts of the application. I know static should not be used, but sometimes it is necessary to access spring context from non-spring-managed beans. E.g. the field looks like this:
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext ctx) {
context = ctx;
}
}
(taken for http://www.dcalabresi.com/blog/java/spring-context-static-class/)
The problem is that when using JUnit (or Spock) framework in integration tests, a new spring context is created for tests that have annotations like #TestPropertySource or #ContextConfiguration, in that case the contexts are cached for other tests with the same configuration (context caching in spring test framework).
However, the static field is only updated when the spring context is created. That means, when a test context is retrieved from the cache, it does not update the static field, of course, because the context was already initialized before being cached. The static field was already overwritten by the last context created from previous test runs with different configuration and so it does not see the same context as the one that starts the test.
The consequence is that part of the test runs in one spring context and from the point it accesses the static field it runs in the other context.
Does anybody have a solution to this problem? Anybody got into the same situation?
I've faced with tha same issue.
The possible solution might be saving context before test and restoring it after.
For convinience it can be done via junit rule:
public class ContextRestoreRule extends ExternalResource {
private ApplicationContext context;
#Override
protected void before() throws Throwable {
context = ApplicationContextProvider.getContext();
}
#Override
protected void after() {
ApplicationContextProvider.setContext(context);
}
}
And in the test (wich modifies context):
#ClassRule
public static ContextRestoreRule contextRestore = new ContextRestoreRule();

ClassBridge with DAO class injected

I have a Hibernate Search ClassBridge where I want to use #Inject to inject a Spring 4.1 managed DAO/Service class. I have annotated the ClassBridge with #Configurable. I noticed that Spring 4.2 adds some additional lifecycle methods that might do the trick, but I'm on Spring 4.1
The goal of this is to store a custom field into the index document based on a query result.
However, since the DAO, depends on the SessionFactory getting initialized, it doesn't get injected because it doesn't exist yet when the #Configurable bean gets processed.
Any suggestions on how to achieve this?
You might try to create a custom field bridge provider, which could get hold of the Spring application context through some static method. When provideFieldBridge() is called you may return a Spring-ified instance of that from the application context, assuming the timing is better and the DAO bean is available by then.
Not sure whether it'd fly, but it may be worth trying.
Hibernate Search 5.8.0 includes support for bean injection. You can see the issue https://hibernate.atlassian.net/browse/HSEARCH-1316.
However I couldn't make it work in my application and I had implemented a workaround.
I have created an application context provider to obtain the Spring application context.
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public static ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
ApplicationContextProvider.context = context;
}
}
I have added it to the configuration class.
#Configuration
public class RootConfig {
#Bean
public ApplicationContextProvider applicationContextProvider() {
return new ApplicationContextProvider();
}
}
Finally I have used it in a bridge to retrieve the spring beans.
public class AttachmentTikaBridge extends TikaBridge {
#Override
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
// get service bean from the application context provider (to be replaced when HS bridges support beans injection)
ApplicationContext applicationContext = ApplicationContextProvider.getApplicationContext();
ExampleService exampleService = applicationContext.getBean(ExampleService .class);
// use exampleService ...
super.set(name, content, document, luceneOptions);
}
}
I think this workaround it's quite simple in comparision with other solutions and it doesn't have any big side effect except the bean injection happens in runtime.

Spring test #ContextConfiguration and static context

I have the following piece of code for my abstract test class (I know XmlBeanFactory with ClassPathResource is deprecated, but it's unlikely to be the case of the problem).
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public abstract class AbstractIntegrationTest {
/** Spring context. */
protected static final BeanFactory context = new XmlBeanFactory(new ClassPathResource(
"com/.../AbstractIntegrationTest-context.xml"));
...
}
It loads the default test configuration XML file AbstractIntegrationTest-context.xml (and then I use autowiring). I also need to use Spring in static methods annotated with #BeforeClass and #AfterClass, so I have a separate context variable pointing to the same location. But the thing is that this is a separate context, which will have different instances of beans. So how can I merge these contexts or how can I invoke Spring's bean initialization defined by #ContextConfiguration from my static context?
I have in mind a possible solution by getting rid of those static members, but I'm curious, if I can do it with relatively small changes to the code.
You are right, your code will produce two application contexts: one will be started, cached and maintained for you by #ContextConfiguration annotation. The second context you create yourself. It doesn't make much sense to have both.
Unfortunately JUnit is not very well suited for integration tests - mainly because you cannot have before class and after class non-static methods. I see two choices for you:
switch to testng - I know it's a big step
encode your setup/tear down logic in a Spring bean included in the context only during tests - but then it will run only once, before all tests.
There are also less elegant approaches. You can use static variable and inject context to it:
private static ApplicationContext context;
#AfterClass
public static afterClass() {
//here context is accessible
}
#Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
context = applicationContext;
}
Or you can annotate your test class with #DirtiesContext and do the cleanup in some test bean:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#DirtiesContext(classMode = AFTER_CLASS)
public abstract class AbstractIntegrationTest {
//...
}
public class OnlyForTestsBean {
#PreDestroy
public void willBeCalledAfterEachTestClassDuringShutdown() {
//..
}
}
Not sure whether you chose any approach here, but I encounter the same problem and solved it another way using Spring test framework's TestExecutionListener.
There are beforeTestClass and afterTestClass, so both equivalent to #BeforeClass and #AfterClass in JUnit.
The way I do it:
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners(Cleanup.class)
#ContextConfiguration(locations = { "/integrationtest/rest_test_app_ctx.xml" })
public abstract class AbstractIntegrationTest {
// Start server for integration test.
}
You need to create a class that extends AbstractTestExecutionListener:
public class Cleanup extends AbstractTestExecutionListener
{
#Override
public void afterTestClass(TestContext testContext) throws Exception
{
System.out.println("cleaning up now");
DomainService domainService=(DomainService)testContext.getApplicationContext().getBean("domainService");
domainService.delete();
}
}
By doing this, you have access to the application context and do your setup/teardown here with spring beans.
Hopefully this help anyone trying to do integration test like me using JUnit + Spring.

spring aspect being applied outside of app context (in stubbed out unit test)

Here's a weird one. I've got a few tests failing because an aspect is being applied, so an autowired service is null, bad things ensue. The issue is that I can't understand how the aspect is even being applied, since in the test I construct the object under test with new.
#RunWith(MockitoJUnitRunner.class)
public class TheControllerTest {
#Spy
private TheController controller = new TheController();
#Mock
private HttpServletRequest request;
#Mock
private ConfigService configService;
....
#Before
public void setup() {
controller.setConfigService(configService);
....
}
#Test
public void testGetAccountsList() throws Exception {
Mockito.when(accountService.getAllAccounts()).thenReturn(Arrays.asList(account1, account2));
Map<String, Object> result = controller.getAccountsList(request);
...
}
}
I'm obviously omitting plenty of code, but really, I just don't understand how, given how controller is instantiated, it could have had the advice applied.
One possible reason could be if you are running this in Eclipse - in a project with ajbuilder enabled, even if you are explicitly expecting Spring AOP through dynamic proxies, ajbuilder would actually perform compile team weaving, and hence you would see advice enhanced classes even using normal "new". Can you please check this, the fix would be to disable "ajbuilder" - here is one reference - JUnit weaving wrong Spring AOP Bean

Resources