Use different Spring test context configuration for different test methods - java

We have a Spring based JUnit test class which is utilizing an inner test context configuration class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ServiceTest.Config.class)
public class ServiceTest {
#Test
public void someTest() {
...
#Configuration
#PropertySource(value = { "classpath:application.properties" })
#ComponentScan({ "..." })
public static class Config {
...
New functionalities have been recently introduced to the Service class, for which the concerned tests should be added to ServiceTest. However these would also require a different test context configuration class to be created (the internals of the existing Config class are fairly complex and change it to serve both old and new tests seems to be be extremely difficult if possible at all)
Is there a way to achieve that certain test methods in one test class would use one config class and other methods would use another? #ContextConfiguration seems to be applicable only on class level, so solution could be to create another test class for the new tests which would utilize its own context configuration class; but it would mean that the same Service class is being covered via two different test classes

With Aaron's suggestion of manually building the context I couldn't find any good examples so after spending some time getting it working I thought I'd post a simple version of the code I used in case it helps anyone else:
class MyTest {
#Autowired
private SomeService service;
#Autowired
private ConfigurableApplicationContext applicationContext;
public void init(Class<?> testClass) throws Exception {
TestContextManager testContextManager = new TestContextManager(testClass);
testContextManager.prepareTestInstance(this);
}
#After
public void tearDown() throws Exception {
applicationContext.close();
}
#Test
public void test1() throws Exception {
init(ConfigATest.class);
service.doSomething();
// assert something
}
#Test
public void test2() throws Exception {
init(ConfigBTest.class);
service.doSomething();
// assert something
}
#ContextConfiguration(classes = {
ConfigATest.ConfigA.class
})
static class ConfigATest {
static class ConfigA {
#Bean
public SomeService someService() {
return new SomeService(new A());
}
}
}
#ContextConfiguration(classes = {
ConfigBTest.ConfigB.class
})
static class ConfigBTest {
static class ConfigB {
#Bean
public SomeService someService() {
return new SomeService(new B());
}
}
}
}

I use these approaches when I'm have to solve this:
Manually build the context in a setup method instead of using annotations.
Move the common test code to a base class and extend it. That allows me to run the tests with different spring contexts.
A mix of the two above. The base class then contains methods to build spring contexts from fragments (which the extensions can override). That also allows me to override test cases which don't make sense or do extra pre/post work in some tests.
Keep in mind that annotations only solve generic cases. You'll have to replicate some or all of their work when you leave the common ground.

Related

Mockito - mock ApplicationContext

I have a Springboot application that looks up the bean from the ApplicationContext at runtime based on the input parameter passed by the user. For this method, I am trying to write Mockito test cases but it is not working and throws NullPointerException.
The class which bootstraps the application:
#SpringBootApplication
public class MyApplication {
private static ApplicationContext appContext;
public static void main(String[] args) {
appContext = SpringApplication.run(MyApplication.class, args);
}
public static ApplicationContext getApplicationContext() {
return appContext;
}
}
Class for which I am trying to write the test cases:
#Service
public class Mailbox {
#Autowired
MailProcessor processor;
public void processUserInput(Envelope object) {
processor.setCommand(MyApplication.getApplicationContext().getBean(object.getAction(), Command.class));
processor.allocateEnvelopes(object);
}
}
And my test case is as below:
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
#ActiveProfiles("test")
public class MailboxTest {
#Mock
MailProcessor processor;
#InjectMocks
Mailbox mailbox;
#Test
public void testProcessUserInput() {
Envelope message = new Envelope();
message.setAction("userAction");
message.setValue("userInput");
doNothing().when(processor).setCommand(any());
doNothing().when(processor).allocateEnvelopes(any());
mailbox.processUserInput(message);
Mockito.verify(processor).allocateEnvelopes(any());
}
}
Whenever I run the test cases it gives the NullPointerException at processor.setCommand(MyApplication.getApplicationContext().getBean(object.getAction(), Command.class)); in Mailbox class. How can I mock the ApplicationContext lookup? Am I missing any mocking step?
Spring wise your code doesn't look good, and in particular is not unit testable. I'll explain:
Your Mailbox service should not be aware of MyApplication at any level. It is an entry point of spring boot application and your business logic should not depend on that.
Its true that you can inject the application context directly into the class. See an example below. Another (more "old-school") option here is using ApplicationContextAware interface in the Mailbox service (see this example). However, its still a bad code IMO:
#Service
public class Mailbox {
private final ApplicationContext ctx;
...
public Mailbox(ApplicationContext ctx) {
this.ctx = ctx;
}
...
}
Even if you resolve it, in general its not a good idea to depend on the ApplicationContext as well. Because this way you become spring dependent and there is no reason to do that in the Mailbox class. The class will become unit testable though.
In terms of resolution:
In spring you can inject a Map<String, Command> into the mailbox (Its a built-in feature in spring) so that the key of the map will be a bean name, exactly an action of your envelop.
So here is the solution (simplified in places not relevant to injection, just to illustrate the idea):
public interface Command {
void execute();
}
#Component("delete") // note this "delete" word - it will be a key in the map in the Mailbox
public class DeleteMailCommand implements Command {
#Override
public void execute() {
System.out.println("Deleting email");
}
}
#Component("send")
public class SendMailCommand implements Command{
#Override
public void execute() {
System.out.println("Sending Mail");
}
}
Note, that all the commands must be driven by spring (which seems to be your case anyway).
Now, the Mailbox will look like this:
#Service
public class Mailbox {
private final Map<String, Command> allCommands;
private final MailProcessor processor;
// Note this map: it will be ["delete" -> <bean of type DeleteMailCommand>, "send" -> <bean of type SendMailCommand>]
public Mailbox(Map<String, Command> allCommands, MailProcessor mailProcessor) {
this.allCommands = allCommands;
this.processor = mailProcessor;
}
public void processUserInput(Envelope envelope) {
Command cmd = allCommands.get(envelope.getAction());
processor.executeCommand(cmd);
}
}
This solution is easily unit testable, because you can populate the map with mock commands if you wish and there is no need to deal with the application context.
Update
I took a look on your test now, and it's also not really good, sorry :)
#RunWith(MockitoJUnitRunner.class) is used to run unit tests (without spring at all). There is no point in placing this annotation in conjunction with #SpringBootTest which runs a full-fledged system test: starts the whole spring boot application, loads configurations and so forth.
So make sure what kind of tests you want to run and use the appropriate annotations.
Can't say for sure without debugging but it looks like MyApplication.getApplicationContext() is returning null.
Instead of storing it in a static variable you should try injecting the ApplicationContext in your #Service class where you need it:
#Autowired
private ApplicationContext appContext;
Try initializing mailbox object by injecting processor before first test.
mailbox = new Mailbox(processor);

How to get different context for different tests in one class?

I have the following test class:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = TestClass.Config.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class TestClass {
#Autowired
private MyClass myClass;
#Test
public void test1() {
myClass.run(); // this is mock
}
#Test
public void test2() {
myClass.run(); // Need real context
}
#Configuration
static class Config {
#Bean
MyClass myClass() {
return mock(MyClass.class);
}
}
}
How can I replace test context to the real context in different test methods?
It is another layer test. It rule of thumb to keep them separated.
You should create separate test class with real context. It should be named TestClassIntegrationTest(for example). Real context tests can be really slow and sametimes they should be ran as last check before commit.
#ContextConfiguration annotation can be defined only on Classes. Here is its definition:
#Target(ElementType.TYPE)
...
public #interface ContextConfiguration {...}
Hence its impossible to define different contexts for different tests in the same class.
In addition, Spring Application context loads before the actual test method runs so you can't really influence it from within the method.
So the best would be indeed running two different test cases (different files)

Spring boot: inject mock into Runner class

I have a spring boot application and some other components which application should interact with. However, in my unit testing I am using just application functionality and I would like to mock outer API calls. I am stuck as I can't find the way to mock case like this:
My start class with main method:
#ComponentScan("com.sample.application")
#SpringBootApplication
public class MyApp implements CommandLineRunner {
#Autowired
private OuterAPI outerAPI;
public static void main(String[] args) {
SpringApplication.run(AdRedirectorMain.class, args);
}
#Override
public void run(String... args) throws Exception {
outerAPI.createInstances();
}
...
}
And here is my test class example:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MyApp.class)
public class MyAppTest {
// any tests
}
I am working with Spring Boot, JUnit, Mockito.
So, I am facing the problem - how could I avoid this method call createInstances() with Mockito, via reflection or in any other way.
Have a look at Mocking and spying beans in the Spring Boot documentation.
You can use #MockBean in your test class to replace an autowired bean with a Mockito mock instance.
You can use #MockBean http://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html
or you can define an interface that you OuterAPI implements then for your test you provide a dummy implementation that makes a dummy call instead of actual call to the outerAPI.createInstances();
Another option that you have is to have configuration class like this:
#Configuration
#Profile(value = {"yourtest-profile"})
public class TestConfiguration{
#Primary
#Bean
public OuterAPI outerAPI() {
return Mockito.mock(OuterAPI.class);
}
}
and put it under scr/test/java

Spring: create mocks instead of real objects

I'm using Spring annotation based configuration in my Play application.
Controllers and DAOs are Spring beans. Controller and DAO layers are defined with different Spring profiles and each layer could be disabled separately.
I'd like to test controller layer in isolation from DAO layer. I've disabled DAO profile and redefined each of DAO beans as a Mockito mock. From functional point of view it works fine, the only thing I don't like is defining mocks manually like this:
#Configuration
#Import(AppContext.class)
public class TestAppContext {
#Bean
public DaoA getDaoA(){
return mock(DaoA.class);
}
//... all dependencies are re-defined manually
}
Is there a way to define package (like with #ComponentScan annotation)
and get all beans in that package as mocks instead of real objects?
UPD:
I'm running tests with FakeApplication (https://www.playframework.com/documentation/2.0/api/java/play/test/FakeApplication.html), so context is started not in the test level, but inside fake application startup.
public class ControllerTest extends WithApplication {
#Before
public void setUp() throws Exception {
start(fakeApplication(new GlobalSettings(){
private ApplicationContext appContext;
public void onStart(Application app) {
appContext = new AnnotationConfigApplicationContext(TestAppContext.class);
}
#Override
public <A> A getControllerInstance(Class<A> clazz) throws Exception {
return appContext.getBean(clazz);
}
}));
}
...
}
I did it like this because I wan't to make the test more reliable and test how controller works in real environment:
#Test
public void testControllerMethod() {
Result result = route(fakeRequest(GET, "/controller/method"));
assertThat(result).is(...);
}
If the number of dependencies you need to mock is huge, you can also use spring-auto-mock.
#ContextConfiguration(classes = { AutoMockRegistryPostProcessor.class, RestOfClasses.class, ... })
#RunWith(SpringJUnit4ClassRunner.class)
public class YourTest {
...
}
As you are creating the ApplicationContext on your own, you can register the postprocessor programmatically:
public void onStart(Application app) {
appContext = new AnnotationConfigApplicationContext(TestAppContext.class);
appContext.getBeanFactory().addBeanPostProcessor(new AutoMockRegistryPostProcessor())
}
Mark your unit-test with #RunWith(SpringJUnit4ClassRunner.class)
Mark your tested class as #InjectMock
Mark you Dao class as #Mock
Make use of Mockito in your project

reduce spring ContextConfiguration boilerplate in tests

I was wondering if there is a way to reduce the amount of boilerplate that we are currently writing for out integration tests.
The main culprit is ContextConfiguration that we send 7 distinct strings into currently.
One of our tests looks like this (payload code removed):
#ContextConfiguration(locations = {"classpath:properties-config.xml",
"classpath:dataSources-config.xml",
"classpath:dao-config.xml",
"classpath:services-config.xml",
"classpath:ehcache-config.xml",
"classpath:test-config.xml",
"classpath:quartz-services.xml"})
#RunWith(SpringJUnit4ClassRunner.class)
#Category(IntegrationTest.class)
public class TerminalBuntsPDFTest {
#Autowired
private JobService jobService;
#Test
public void testCode() throws SystemException {
assertTrue("Success", true);
}
}
And the specification of what xml files to load takes up a lot of space. We are in a (very slow) process of migrating away from xml towards annotations, but there is a lot of work left to do in that project.
We are using spring 3.2.
The annotation based approach is to create a Spring Configuration Java class like this:
#Configuration("testConfig")
#ImportResource({
"dataSources-config.xml",
"dao-config.xml",
"services-config.xml"
})
public class TestConfiguration {
// TO create a spring managed bean
#Bean
MyBean myBean() {
return new MyBean();
}
}
Then you can annotate your test class like so to load the configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(
classes=TestConfiguration.class,
loader=AnnotationConfigContextLoader.class
)
#Category(IntegrationTest.class)
public class TerminalBuntsPDFTest {
This is just a light example that probably won't compile but should get you on the right track
Some relevant docs:
http://www.tutorialspoint.com/spring/spring_java_based_configuration.htm
http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html
What about such pattern:
#ContextConfiguration(locations = {"classpath:properties-config.xml",
"classpath:dataSources-config.xml",
"classpath:dao-config.xml",
"classpath:services-config.xml",
"classpath:ehcache-config.xml",
"classpath:test-config.xml",
"classpath:quartz-services.xml"})
#RunWith(SpringJUnit4ClassRunner.class)
#Category(IntegrationTest.class)
public abstract class BaseTest {
}
// ....
public class TerminalBuntsPDFTest extends BaseTest {
#Autowired
private JobService jobService;
#Test
public void testCode() throws SystemException {
assertTrue("Success", true);
}
}
// ....
public class TerminalBuntsPDFTest2 extends BaseTest {}
This will allow you to place configuration only once in parent abstract class.

Categories

Resources