How to run code before SpringJUnit4ClassRunner context initialization? - java

In my application I initialize a property before spring application startup as follows:
MapLookup.setMainArguments(new String[] {"logging.profile", profile}); //from args
SpringApplication.run(source, args);
(just for reference: it is used for log4j2 logging, which must be set before spring starts to initialize).
Now I want to run an #IntegrationTest, but use the same logging configuration. Obviously I cannot use the code above, as a JUnit test is not executed using SpringApplication.run.
So, how could I initialize code before a #RunWith(SpringJUnit4ClassRunner.class) starts?
Note: BeforeClass does not work as this is executed after spring context startup.

You can run the initialization in a static initializer. Static initializer will run after JUnit loads the test class and before JUnit reads any annotations on it.
Alternatively you can extend SpringJUnit4ClassRunner with your own Runner initialize in it first and then run SpringJUnit4ClassRunner

I had a slightly different problem. I need to deploy something to my service after the Spring context is loaded. Solution use a custom config class for the test and run the deployment within a #PostConstruct Method.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestConfig.class, loader = AnnotationConfigContextLoader.class)
public class JunitTest {
#Configuration
#ComponentScan(basePackages = { "de.foo })
public static class TestMConfig {
#Autowired
private DeploymentService service;
#PostConstruct
public void init() {
service.deploy(...);
}
}
#Test
public void test() {
...
}
}
Maybe this helps, someone, sometime, somewhere ;)

Related

Run method before loading ApplicationContext with SpringJUnit4ClassRunner

I'm a new engineer working on a large legacy system tasked with setting up a series of unit tests making use of the application's existing Spring configuration. This legacy system depends on an additional configuration system called LegacyConfig that pre-dates the adoption of Spring. As the adoption of Spring was built on top of LegacyConfig, many of the bean definitions depend on this configuration system already having been initialized through the use of expressions in XML definitions. This LegacyConfig is typically initialized in our existing unit tests with a single call: LegacyConfig.initialize().
I have my unit test class set up like so:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {LegacySystemConfig.class, TestClass.BeanConfig.class})
public class TestClass {
#Autowired
SomeTestingDependency someTestingDependency;
#BeforeClass
public static void setup() {
...
}
#Test
public void myTest() {
...
}
....
#Configuration
static class BeanConfig {
#Bean
#Primary
public OutsideServiceClient mockServiceClient() {
return Mockito.mock(OutsideServiceClient.class);
}
}
}
Since we have this dependency on LegacyConfig having been initialized, attempting to run the tests results in a java.lang.IllegalStateException: Failed to load ApplicationContext exception as the Spring configuration attempts to fetch a value from LegacyConfig, receives null instead, and fails to convert null to a primitive boolean.
I've attempted to place the LegacyConfig initialization within the setup() method, but this fails because the ApplicationContext is attempted be loaded before setup() is called, resulting in the exception again.
Is there a way for me to run the LegacyConfig setup before the test runner or ApplicationContext are initialized?

Testing a Spring Boot command line application

I'd like to test my Spring Boot command line application. I would like to mock certain beans (which I was able to do by annotating #ContextConfiguration(classes = TestConfig.class) at the top of my test class. In TestConfig.class, I override the beans that I would like to mock. I'd like Spring Boot to find the rest of the components. This seems to work.
The problem is that when I run the test, the entire application starts up as normal (ie. the run() method is called).
#Component
public class MyRunner implements CommandLineRunner {
//fields
#Autowired
public MyRunner(Bean1 bean1, Bean2 bean2) {
// constructor code
}
#Override
public void run(String... args) throws Exception {
// run method implementation
}
I've tried to override the MyRunner #Bean and put it in TestConfig.class, but that doesn't seem to work. I understand that I'm loading the regular application context, but that's what I'd like to do (I think?) since I would like to re-use all (or most) of the #Component I created in my Application, and only mock a tiny subset.
Any suggestions?
EDIT:
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The answer was simpler than I thought. Add the MockBean in
#TestConfiguration
public class TestConfig {
#MockBean
private MyRunner myRunner;
}
We can use the #MockBean to add mock objects to the Spring application context. The mock will replace any existing bean of the same type in the application context.
So MyRunner.run() is never called but I can still use all the other beans in my application.
CommandLineRunners are ordinary beans with one exception:
After the application context is loaded, spring boot finds among all its beans the beans that implement this interface and calls their run method automatically.
Now, I would like you to ask to do the following:
Remove ContextConfiguration from the test and place a breakpoint in constructor of MyRunner. The test should look like this:
#RunWith(SpringRunner.class) // if you're on junit 4, adjust for junit 5 if you need
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyTest {
#Autowired
private MyRunner myRunner;
#Test
public void testMe() {
System.out.println("hello");
}
}
Run the test and make sure that myRunner is loaded and its run method is called
Now mock this class with MockBean annotation:
#RunWith(SpringRunner.class) // if you're on junit 4, adjust for junit 5 if you need
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyTest {
#MockBean
private MyRunner myRunner;
#Test
public void testMe() {
System.out.println("hello");
}
}
Run the test. Make sure that run method is not running. Your Application Context now should contain a mock implementation of your component.
If the above works, then the problem is with TestConfig and ContextConfiguration annotation. In general when you run without ContextConfiguration you give spring boot test engine a freedom to mimic the application context started as if its a real application (with autoconfigurations, property resolution, recursive bean scanning and so forth).
However if you put ContextConfiguration, spring boot test doesn't work like this - instead it only loads the beans that you've specified in that configuration. No Autoconfigurations, no recursive bean scanning happens for example.
Update
Based on OP's comment:
It looks like the MyRunner gets loaded when you put #ContextConfiguration because of component scanning. Since you have an annotation #Component placed on MyRunner class it can be discovered by Spring boot engine.
In fact there is a "dangerous" mix of two types of beans definitions here:
1. The beans defined with #Bean in #Configuration annotation
2. The beans found during component scanning.
Here is the question for you: If you don't want to mimic the application startup process and instead prefer to load only specific beans, why do you use #SpringBootTest at all? Maybe you can achieve the goal with:
#RunWith(SpringRunner.class)
#ContextConfiguration(YourConfig.class)
public class MyTest {
...
}
One way you could do this is to have 2 classes with the main method, one which sets up the "normal" context, and another that sets up the "mock" context:
Normal App Context, uses the usual Application
#SpringBootApplication(scanBasePackages = "com.example.demo.api")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public Foo foo() {
return new Foo("I am not mocked");
}
#Bean
public Bar bar() {
return new Bar("this is never mocked");
}
}
Add another Application class that overrides the normal context with the mocked one
#SpringBootApplication(scanBasePackageClasses = {MockApplication.class, Application.class})
#Component
public class MockApplication {
public static void main(String[] args) {
SpringApplication.run(MockApplication.class, args);
}
#Bean
public Foo foo() {
return new Foo("I am mocked");
}
}
When you run Application.main Foo will be "I am not mocked", when you run MockApplication.main() it will be "I am mocked"

How to initialize spring configurations outside of JUnit?

I have code that can be run properly as a JUnit test case. However, when I put the same test code inside a main class, spring configuration do not properly load the objects.
Spring code looks like this:
#ContextConfiguration(locations = { "classpath:/fileonly-sens-services.xml" })
#RunWith(SpringJUnit4ClassRunner.class)
public class AppTest extends ContextBuilder {
#BeforeClass
public static void setup() {
System.setProperty("app-
init.properties","classpath:test.app.properties");
#Test
someTestMethod()
}
I think it is a very simple problem but I cannot get it working outside of JUnit! Thanks for the help!
In your application you will have to create an ApplicationContext. The specifics of how to do it, depends on what kind of application you are building.
If you are building a command-line application, you can instantiate ClassPathXmlApplicationContext and use it to instantiate needed beans.
If you are building a web application, you can use ContextLoaderListener to load context during your application initialization.

how to use spring's #IntegrationTest in main method?

i have a spring boot application. sometimes i need to launch long running report generation. the easiest way was to create an #IntegrationTest to use spring's #Autowire:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#SpringApplicationConfiguration(classes=Application.class)
#IntegrationTest
public class ReportGenerationTest {
#Autowired MyService myService;
#Value("classpath:/test.txt") Resource testData; //it's in test, not in src
#Test
public void generate_report() {
report(myService, testData);
}
}
it works perfectly. but i don't want it to be run with every build. i also don't want to add/remove #Ignore as sooner or later someone will accidentally commit it without #Ignore and i don't want to do any code editing just to run reports
ideally i would like it to be a main method run on demand. but how can i manually create a spring context for the src service and test resources?
i need something like:
public class MyReport {
public static void main(String[] args) {
ReportGenerationTest reportGenerator = getAutowiredInstanceFromSpring();
reportGenerator.generate_report();
}
}

integration testing spring service layer based on migrated data

#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"/applicationContext-test.xml"})
#Transactional
public class MyServiceTest {
#Resource(name="myService")
public MyService myService;
#Test
public void testSeomthing() {
//do some asserts using myService.whatever()
}
}
However the tests are based on data I migrate in, so every time I run my suite of tests I want to execute my unrelated migration code. I don't want to run a #Before in each test class. I want to run it once at beginning of complete test process, where can I put this ?
I would advice you to create a test bean somewhere with startup logic invoked in #PostConstruct:
#Service
public class TestBean {
#PostConstruct
public void init() {
//startup logic here
}
}
Obviously this bean should only be created for tests, the easiest way to achieve this is to place it in src/test/java in a package that is component-scanned by Spring for #Service-annotated classes.
Note: you must remember that #PostConstruct is not running in a transaction! See How to call method on spring proxy once initialised.
JUnit also offers a #BeforeClass annotation which you can place on a static method to initialize resources just once.

Categories