I have an application based on Spring 4.3.28 (i.e. not Spring Boot!) and I want to migrate my integration tests to Cucumber.
I’ve followed this tutorial and adapted it to plain Spring.
The tests I’ve written so far are working fine (Spring context is initialized etc.), but as soon as there are request-scoped beans involved, they stop working:
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you
referring to request attributes outside of an actual web request, or processing a
request outside of the originally receiving thread? If you are actually operating
within a web request and still receive this message, your code is probably running
outside of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current request.
I’ve created a small sample project
that tries to reproduce the problem.
There is one context configuration class called AppConfig:
#Configuration
public class AppConfig {
#Bean
#Scope("request“) // when this line is removed, the test succeeds
public ExampleService exampleService() {
return new ExampleService();
}
#Bean("dependency")
#Scope("request") // when this line is removed, the test succeeds
public String dependencyBean() {
return "dependency bean";
}
}
The ExampleService is request-scoped, and gets one request-scoped bean injected by #Autowired:
public class ExampleService {
#Autowired
#Qualifier("dependency")
String dependencyBean;
public String process() { return "I have a "+dependencyBean; }
}
For the tests, I have one Spring-annotated superclass:
#ContextConfiguration(classes = AppConfig.class)
#CucumberContextConfiguration
#WebAppConfiguration
public class TestBase {
#Autowired
public ExampleService underTest;
}
There’s also a plain Spring test that runs just fine:
#RunWith(SpringRunner.class)
public class ExampleServicePlainSpringTest extends TestBase {
#Test
public void whenProcessingDataThenResultShouldBeReturned() {
assertThat(this.underTest.process()).isEqualTo("I have a dependency bean");
}
}
The Cucumber test is executed by this test class stub:
#RunWith(Cucumber.class)
public class ExampleServiceCucumberTest extends TestBase {}
The actual cucumber step definitions are here:
public class CucumberStepDefinitions extends TestBase {
private String result;
#When("I process data")
public void iProcessData() {
result = this.underTest.process();
}
#Then("the result should be returned")
public void checkResult() {
assertThat(result).isEqualTo("I have a dependency bean");
}
}
The .feature file for Cucumber is in the src/test/resources directory under the same package name as the step definitions class:
Feature: Example
Scenario: Example service bean returns dependency
When I process data
Then the result should be returned
Usually when I encountered the „no thread-bound request found“ error, it was because the #WebAppConfiguration annotation was missing, or when I tried to inject a request-scoped bean into a non-request scoped bean. But that’s not the case here.
What am I doing wrong?
I was able to figure out how to resolve it; the updated code is in the github repository linked in the question.
When using the SpringRunner, the request context is initialized in a ServletTestExecutionListener that is implicitly added to the list of TestExecutionListeners for the test.
The initialization happens in the beforeTestMethod() method of that listener.
However, as #M.P.Korsanje correctly remarked in the comments (thanks!), Cucumber doesn't have test methods, so beforeTestMethod() is never executed.
My solution was to add a custom subclass of ServletTestExecutionListener as a TestExecutionListener that delegates the beforeTestClass() call to the beforeTestMethod():
public class ClassLevelServletTestExecutionListener extends ServletTestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
super.beforeTestMethod(testContext);
}
#Override
public void afterTestClass(TestContext testContext) throws Exception {
super.afterTestMethod(testContext);
}
}
And in ExampleServiceCucumberTest:
#ContextConfiguration(classes = {AppConfig.class})
#CucumberContextConfiguration
#WebAppConfiguration
#TestExecutionListeners(ClassLevelServletTestExecutionListener.class)
// extend the Spring class to get the default TestExecutionListeners
public class TestBase extends AbstractJUnit4SpringContextTests {
#Autowired
public ExampleService underTest;
}
Related
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);
I am working on multi module maven project with SpringBoot framework.
The project is divided in 4 modules: rest module,service module,repository module, domain module.
I am trying to write a unit test for a configuration class in java which is located in the service module.
I have simplified the case to get rid of business logic complexity. The configuration class is like below:
#Configuration
#ConfigurationProperties(prefix = "x.y.feature", ignoreInvalidFields = false)
public class FeatureConfig {
private String featureUrl;
public String getFeatureUrl() {
return featureUrl;
}
public void setFeatureUrl(String FeatureUrl) {
this.featureUrl = featureUrl;
}
}
The properties file is application.properties.
x.y.feature.featureUrl=featureUrl
And below is the unit test that is not working.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { FeatureConfigTest.class })
public class FeatureConfigTest {
#Autowired
private FeatureConfig featureConfig;
#Test
public void testgetFeatureUrl() {
String expected ="featureUrl";
assertEquals(expected,featureConfig.getFeatureUrl());
}
}
When i run the unit test , it throws the exception below:
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108)
The problem is that you are trying to use object of FeatureConfig
from the application context. You haven't initialized the application context.
When you use the #Autowire annotation, you are mapping field 'featureConfig'
with instance of type FeatureConfig class located in the application context.
To bypass this error you need initialize the application context.
First thing , create static class inside the test class that will help us not to load the
whole application context. Because for this test case you don't need to load the whole application.
First thing , create static class inside the test class that will help us not to load the
whole application context. Because for this test case you don't need to load the whole application.
#EnableConfigurationProperties(FeatureConfig.class)
public static class TestConfiguration {
}
After that you start the application context , but with passing as configuration the static class
you created. This is done not to load the whole application context.
#SpringBootTest(classes = { FeatureConfigTest.TestConfiguration class })
Copy and paste the changes below in your test class and everything should work ok.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { FeatureConfigTest.TestConfiguration class })
public class FeatureConfigTest {
#Autowired
private FeatureConfig featureConfig;
#Test
public void testgetFeatureUrl() {
String expected ="featureUrl";
assertEquals(expected,featureConfig.getFeatureUrl());
}
#EnableConfigurationProperties(FeatureConfig.class)
public static class TestConfiguration {
}
}
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
I have a class which contains a few service activator methods as follows:
#MessageEndpoint
public class TestService {
#ServiceActivator
public void setComplete(Message<String> message){
//do stuff
}
}
In the integration flow, one of the channels call one of these methods:
#Bean
public TestService testService() {
return new TestService();
}
#Bean
public IntegrationFlow testFlow() {
return IntegrationFlows.from("testChannel")
.handle("testService", "setComplete")
.handle(logger())
.get();
}
I'm writing a unit test for this flow and using Mockito for mcoking the service activator class:
#ContextConfiguration(classes = IntegrationConfig.class)
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext
public class AppTest {
#Mock
private TheGateway startGateway;
#Mock
private TestService testrvice;
#Autowired
#Qualifier("testChannel")
DirectChannel testChannel;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test()
public void testMessageProducerFlow() throws Exception {
Mockito.doNothing().when(startGateway).execute("test");
startGateway.execute("test");
Mockito.verify(startGateway).execute("test");
TestChannel.send(new GenericMessage<>("test"));
Mockito.verify(testService).setComplete(new GenericMessage<>("test"));
}
}
When I don't mock the TestService, it executes the flow without issues.
Any guideance on how to Mock the Service activator class would be helpful.
UPDATE:
When I mock it (as shown in snippet above), it does not call the mocked object, instead executes the actual stuff, and the last line Mockito.verify(testService)... asserts that the mock testService was never called.
First of all you misunderstood how Spring Test Framework works.
#ContextConfiguration(classes = IntegrationConfig.class) loads the config as is without any modification and start an application context based on that config.
According to the first condition your .handle("testService", "setComplete") uses testService() #Bean not #Mock
Only after the test applicationContext startup all those #Mocks and #Autowireds start working.
In other words your mocking doesn't change anything in the original IntegrationConfig.
In the Framework with use reflection to retrieve some field of the particular bean to replace it with the mock. But it isn't so easy way.
I suggest you to distinguish the Integration and Service configuration and use two different classes for production and for testing. Something like this:
The testService() #Bean must be moved from the IntegrationConfig to the new #Configuration class for production.
The TestServiceConfig may look like this:
#Bean
public TestService testService() {
return Mockito.mock(new TestService());
}
And finally your AppTest should be modified like this:
#ContextConfiguration(classes = {IntegrationConfig.class, TestServiceConfig.class})
....
#Autowired
private TestService testrvice;
That's everything is just because the application context and unit test scopes are on the different levels.
There have been many questions regarding request scoped management in unit tests and mainly answer is to do not test the scope management, as its a Spring Framework task and it should take care that it works properly. So advice, for example, would be to replace the request scope with thread or prototype type scope in the XML configuration file.
For most of the tests its enough, there are no complaints about not registered "request" scope and tests are running fine. But I do have one case where it is not enough.
Consider following case:
#Component
#Scope("request")
public class MyService {
#Autowired
private MyComponent component;
public void doSomething(String param) {
component.doTheThing(param);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:my-scope-tweaks.xml"})
public class MyServiceTest {
#Autowired
private MyService service;
#Autowired
private MyComponent component;
#Test
public void test1() {
service.doSomething("aaa");
assertEquals("AAA", component.getTheThing());
}
#Test
public void test1() {
service.doSomething("bbb");
assertEquals("BBB", component.getTheThing());
}
}
I want to test MyService, which is request-scoped. MyComponent is request scope as well.
Variant A
If I replace the request scope with SimpleThreadScope, then in by both tests I would receive the same instance of MyService and MyComponent, so for example test2() could receive bad results from MyComponent as it could internally contain some internal "trash" from previous test1()
Variant B
If I replace request scope with prototype scope - I would get the case where my test methods are receiving different instances of MyComponent as MyService does - so I cannot perform any assertions on them.
So what I would need is kind of test method-related request scope, where all request-scoped beans remain just during the test1() method and then gets destroyed, so within next test2() they would be created newly again.
Is it possible?
This may not be what you're looking for, but why are you using Spring to manage your test classes in the first place? That seems like overkill to me. For unit testing, you shouldn't need a DI container. Just mock the dependencies and focus on the encapsulated functionality of the Component or Service you're testing.
You won't be able to do that, though, while using field injection. You'll need to convert to constructor or method injection.
It is possible to receive a new context for every method in your unit test by annotating the method with #DirtiesContext. This allows you to manipulate beans in the application context for one unit test and not have it affect the others.
Documentation
Your example annotated with #DirtiesContext:
#Component
#Scope("request")
public class MyService {
#Autowired
private MyComponent component;
public void doSomething(String param) {
component.doTheThing(param);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:my-scope-tweaks.xml"})
public class MyServiceTest {
#Autowired
private MyService service;
#Autowired
private MyComponent component;
#Test
#DirtiesContext
public void test1() {
service.doSomething("aaa");
assertEquals("AAA", component.getTheThing());
}
#Test
#DirtiesContext
public void test2() {
service.doSomething("bbb");
assertEquals("BBB", component.getTheThing());
}
}
If all of your test methods within your class require a new context you can also just annotate the class as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:my-scope-tweaks.xml"})
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class MyServiceTest {
//Tests Here...
}