I have an ItemProcessor which has a #BeforeStep method to access the ExecutionContext:
public class MegaProcessor implements ItemProcessor<String, String> {
private ExecutionContext context;
#BeforeStep
void getExecutionContext(final StepExecution stepExecution) {
this.context = stepExecution.getExecutionContext();
}
#Override
public String process(final String string) throws Exception {
// ...
}
}
The unit test for this class:
#ContextConfiguration(classes = MegaProcessor.class)
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class })
#RunWith(SpringRunner.class)
public class MegaProcessorTest {
#Autowired
private MegaProcessor sut;
public StepExecution getStepExecution() {
StepExecution execution = MetaDataInstanceFactory.createStepExecution();
execution.getExecutionContext().put("data", "yeah");
return execution;
}
#Test
public void MegaProcessor() throws Exception {
assertNotNull(sut.process("pew pew"));
}
}
When I debug the test run, context is null and the #BeforeStep method is never called. Why is that and how to achieve that?
Why is that
If you want to use the StepScopeTestExecutionListener, the tested component should be step-scoped (See Javadoc). It's not the case in your example. But this is not the real issue. The real issue is that the method annotated with #BeforeStep will be called before the step in which your processor is registered is executed. In your test case, there is no step running so the method is never called.
how to achieve that?
Since it is a unit test, you can assume the step execution will be passed to your item processor by Spring Batch before running the step and mock/stub it in your unit test. This is how I would unit test the component:
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.StepExecution;
import static org.junit.Assert.assertNotNull;
public class MegaProcessorTest {
private MegaProcessor sut;
#Before
public void setUp() {
StepExecution execution = MetaDataInstanceFactory.createStepExecution();
execution.getExecutionContext().put("data", "yeah");
sut = new MegaProcessor();
sut.getExecutionContext(execution); // I would rename getExecutionContext to setExecutionContext
}
#Test
public void MegaProcessor() throws Exception {
assertNotNull(sut.process("pew pew"));
}
}
The StepScopeTestExecutionListener is handy when you have step-scoped components that use late-binding to get values from the step execution context. For example:
#Bean
#StepScope
public ItemReader<String> itemReader(#Value("#{stepExecutionContext['items']}") String[] items) {
return new ListItemReader<>(Arrays.asList(items));
}
A unit test of this reader would be something like:
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
#ContextConfiguration(classes = StepScopeExecutionListenerSampleTest.MyApplicationContext.class)
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class })
#RunWith(SpringRunner.class)
public class StepScopeExecutionListenerSampleTest {
#Autowired
private ItemReader<String> sut;
public StepExecution getStepExecution() {
StepExecution execution = MetaDataInstanceFactory.createStepExecution();
execution.getExecutionContext().put("items", new String[] {"foo", "bar"});
return execution;
}
#Test
public void testItemReader() throws Exception {
Assert.assertEquals("foo", sut.read());
Assert.assertEquals("bar", sut.read());
Assert.assertNull(sut.read());
}
#Configuration
static class MyApplicationContext {
#Bean
#StepScope
public ItemReader<String> itemReader(#Value("#{stepExecutionContext['items']}") String[] items) {
return new ListItemReader<>(Arrays.asList(items));
}
/*
* Either declare the step scope like the following or annotate the class
* with `#EnableBatchProcessing` and the step scope will be added automatically
*/
#Bean
public static org.springframework.batch.core.scope.StepScope stepScope() {
org.springframework.batch.core.scope.StepScope stepScope = new org.springframework.batch.core.scope.StepScope();
stepScope.setAutoProxy(false);
return stepScope;
}
}
}
Hope this helps.
Related
Is it somehow possible to declare a RestController in a test context, preferably as a inner class of a spring boot test? I do need it for a specific test setup. I already did try following simple example as a POC:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
#ExtendWith(SpringExtension.class)
#WebMvcTest(ExampleTest.TestController.class)
#AutoConfigureMockMvc
#AutoConfigureWebClient
public class ExampleTest {
#Autowired
private MockMvc mockMvc;
#Test
public void exampleTest() throws Exception {
ResultActions resultActions = this.mockMvc
.perform(get("/test"));
resultActions
.andDo(print());
}
#RestController
public static class TestController {
#GetMapping("/test")
public String test() {
return "hello";
}
}
}
Testing the endpoint via MockMvc delivers 404 though. Am i missing something?
The problem is, TestController is not loaded to the application context. It could be solved by adding
#ContextConfiguration(classes= ExampleTest.TestController.class)
The test will look like:
#ExtendWith(SpringExtension.class)
#WebMvcTest(ExampleTest.TestController.class)
#ContextConfiguration(classes= ExampleTest.TestController.class)
#AutoConfigureMockMvc
#AutoConfigureWebClient
public class ExampleTest {
#Autowired
private MockMvc mockMvc;
#Test
public void exampleTest() throws Exception {
ResultActions resultActions = this.mockMvc
.perform(get("/test"));
resultActions
.andDo(print());
}
#RestController
public static class TestController {
#GetMapping("/test")
public String test() {
return "hello";
}
}
}
And the output:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"5"]
Content type = text/plain;charset=UTF-8
Body = hello
Forwarded URL = null
Redirected URL = null
Cookies = []
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
#ActiveProfiles(profiles = "test")
#RunWith(MockitoJUnitRunner.class)
public class ConfigurationTest {
#Autowired
private Environment environment;
#Test
public void test1() throws Exception {
environment.getProperty("bhavya",Boolean.class);
}
#Configuration
#Profile("test")
#ComponentScan(basePackages={"com.bhavya.test"})
public static class EnvironmentServiceTestConfiguration{
#Bean
#Primary
public Environment environment(){
return Mockito.mock(Environment.class);
}
}
}
I also tried putting EnvironmentServiceTestConfiguration as a non-inner non-static class, but didn't help.
Here is what I tried in a separate class:
#RunWith(MockitoJUnitRunner.class)
#Profile("test")
#Configuration
class EnvironmentServiceTestConfiguration{
#Bean
#Primary
public Environment environment(){
return Mockito.mock(Environment.class)
}
}
didn't work either
The test class is located in test/java/com.bhavya.test package.
I am trying to run this particular test test1
This is my first test of such kind. I have never before used AnnotationConfigContextLoader.class, enlighten me.
Stacktrace :
java.lang.NullPointerException
at Line number where I have statement :
environment.getProperty("bhavya",Boolean.class);
Try using #RunWith(SpringJUnit4ClassRunner.class)
Here is an example explaining how can be done
https://www.mkyong.com/unittest/junit-spring-integration-example/
Here is a code sample that works for the sample in the question:
package com.example.demo.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
#RunWith(SpringJUnit4ClassRunner.class)
public class ConfigurationTest {
#Autowired
private Environment environment;
#Test
public void test1() throws Exception {
System.out.println(environment.getProperty("bhavya",Boolean.class));
}
#Configuration
#ComponentScan(basePackages={"com.example.demo.test"})
public static class EnvironmentServiceTestConfiguration{
#Bean
#Primary
public Environment environment(){
Environment env = Mockito.mock(Environment.class);
when(env.getProperty(eq("bhavya"), eq(Boolean.class))).thenReturn(true);
return env;
}
}
}
Try below annotations for your test class
#ContextConfiguration(classes = Config.class)
#SpringBootTest
#ExtendWith(SpringExtension.class)
#RunWith(SpringJUnit4ClassRunner.class)
I know I should not be testing void methods like this, but I am just testing Mockito.doNothing() as of now with a simple example.
My Service class:
#Service
public class Service{
#Autowired
private Consumer<String, String> kafkaConsumer;
public void clearSubscribtions(){
kafkaConsumer.unsubscribe();
}
}
My Test class:
#MockBean
private Consumer<String, String> kafkaConsumer;
#Test
public void testClearSubscriptions() {
Service service = new Service();
Mockito.doNothing().when(kafkaConsumer).unsubscribe();
service.clearSubscriptions();
}
The test keeps failing with a null pointer exception. When I debugged it, it goes into the clearSubscription method of the service class, and there on the line of kafkaConsumer.unsubscribe(), kafkaConsumer is null. But I mocked the consumer, why is it throwing null pointer exception and I should be skipping over that method, right?
Edit:
All the declarations of the class:
#Autowired
private Consumer<String, String> kafkaConsumer;
#Autowired
private Service2 service2;
private final Object lock = new Object();
private static Logger logger = LoggerFactory.getLogger(Service.class);
private HashMap<String, String> subscribedTopics = new HashMap<>();
Figured out what was wrong, I needed to auto wire the service
You are instantiating a new service Service service = new Service(); but from what I can see you are never injecting the mock bean into the new service.
Here is a sample of what I think you could do if you are using mockito only and dont need to instantiate a spring container (used a single class for ease of example dont do this in actual code):
package com.sbp;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#RunWith(MockitoJUnitRunner.class) // run with mockitos runner so annotations are processed
public class MyServiceTest {
public interface Consumer<T, R> {
public void unsubscribe();
}
#Service
public class KafkaConsumer implements Consumer<String, String> {
#Override
public void unsubscribe() {
}
}
#Service
public class MyService {
#Autowired
private Consumer<String, String> kafkaConsumer;
public void clearSubscriptions() {
kafkaConsumer.unsubscribe();
}
}
#Mock // tell mockito that this is a mock class - it will instantiate for you
private Consumer<String, String> kafkaConsumer;
#InjectMocks // tell mockito to inject the above mock into the class under test
private MyService service = new MyService();
#Test
public void testClearSubscriptions() {
service.clearSubscriptions();
Mockito.verify(kafkaConsumer, Mockito.times(1)).unsubscribe();
}
}
If you need an example via Spring using MockBean or without and dependencies, let me know and I can post.
UPDATED: adding sample using spring junit runner and using spring boot's mockbean annotation
package com.sbp;
import com.sbp.MyServiceTest.TestContext.MyService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class) // run with spring
#SpringBootTest(classes = MyServiceTest.TestContext.class) // make it a spring boot test so #MockBean annotation is processed, provide a dummy test context class
public class MyServiceTest {
public interface Consumer<T, R> {
public void unsubscribe();
}
#Configuration
public static class TestContext {
#Service
public class KafkaConsumer implements Consumer<String, String> {
#Override
public void unsubscribe() {
}
}
#Service
public class MyService {
#Autowired
private Consumer<String, String> kafkaConsumer;
public void clearSubscriptions() {
kafkaConsumer.unsubscribe();
}
}
}
#MockBean // this will create a mockito bean and put it in the application context in place of the Kafka consumer bean defined in the TestContext class
private Consumer<String, String> kafkaConsumer;
#Autowired // inject the bean from the application context that is wired with the mock bean
private MyService myService;
#Test
public void testClearSubscriptions() {
myService.clearSubscriptions();
Mockito.verify(kafkaConsumer, Mockito.times(1)).unsubscribe();
}
}
I try to use Springs own Dependency Injection in a Junit test case:
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.binarisinformatik.api.AppConfig;
import org.binarisinformatik.satzrechner.SatzRechner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=AppConfig.class)
//#SpringApplicationConfiguration(classes = {AppConfig.class})
public class SatzRechnerTest {
#Autowired
private SatzRechner satzRechner; //SUT
#Before
public void setUp() {
// AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SatzRechnerTest.class);
//satzRechner=context.getBean(SatzRechner.class);
}
#Test
public void addiere_satz_4komma6_zu_zahlwert_10() {
assertThat("Addition von \"4,6\" ergibt nicht 10!",
satzRechner.summe("4,6"), is(equalTo(10)));
}
Im testing a class names SatzRechner in which Spring should also autowire some variables. Here is my Class under test:
#Component
public class SatzRechner {
#Autowired //#Inject
private Rechner taschenRechner;
#Autowired
private Zahlenfabrik zahlenfabrik;
public Integer summe(String zeichenSatz) {
return taschenRechner.summe(zahlenfabrik.erzeugeZahlen(zeichenSatz));
}
}
And AppConfig.class which is using as Configurationfile looks like that:
#Configuration
#ComponentScan(value={"org.binarisinformatik"})
public class AppConfig {
}
What is here the problem?
If you want to use a Spring configuration class, this one must have beans definitions. Please find an example below :
Test class:
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.binarisinformatik.api.AppConfig;
import org.binarisinformatik.satzrechner.SatzRechner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=AppConfig.class)
public class SatzRechnerTest {
#Autowired
private SatzRechner satzRechner;
#Test
public void addiere_satz_4komma6_zu_zahlwert_10() {
assertThat("Addition von \"4,6\" ergibt nicht 10!",
satzRechner.summe("4,6"), is(equalTo(10)));
}
}
Configuration class :
You have to declare #Bean annotated methods. These beans are managed by Spring container.
#Configuration
public class AppConfig {
// Beans present here will be injected into the SatzRechnerTest class.
#Bean
public SatzRechner satzRechner() {
return new SatzRechner();
}
#Bean
public Rechner taschenRechner() {
return new TaschenRechner();
}
#Bean
public Zahlenfabrik zahlenfabrik() {
return new Zahlenfabrik();
}
}
Note : I let you properly handle returned types here and beans parameters (if present in your context).
There are two things you have to ensure before you run the test case successfully:
1) Classes SatzRechner, Rechner & Zahlenfabrik should be under "org.binarisinformatik" package
2) Classes Rechner & Zahlenfabrik should also be annotated with #Component as SatzRechner.
This test is failing but I don't know why or how to fix it. If I hit a break point and call mockingContext.getBean(Repository.class), it does return my mock object, but for some reason the ProductionCode returned in createProductionCodeWithMock still has the real Repository autowired into it. I can only suspect that I need to do something special/extra to have an effect on autowired beans, but I don't know what it is. Why isn't this test passing, and how can I make it pass?
For what it's worth, I'm well aware of all the answers to this question. I still want to know why this test isn't working. I am using Spring 3.x.
Here's the test:
package mavensnapshot;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class ProductionCodeTest {
#Test
public void testReplaceRepositoryWithMock() {
Repository mockRepository = mock(Repository.class);
ProductionCode productionCode = createProductionCodeWithMock(mockRepository);
productionCode.doSomething();
verify(mockRepository).save();
}
private ProductionCode createProductionCodeWithMock(Repository mockRepository) {
GenericApplicationContext mockingContext = new GenericApplicationContext();
mockingContext.getBeanFactory().registerSingleton(Repository.class.getName(), mockRepository);
mockingContext.setParent(new ClassPathXmlApplicationContext("/beans.xml"));
return mockingContext.getBean(ProductionCode.class);
}
}
Here's my production code:
package mavensnapshot;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("/beans.xml");
ProductionCode productionCode = context.getBean(ProductionCode.class);
productionCode.doSomething();
}
}
package mavensnapshot;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class ProductionCode {
#Autowired
private Repository repository;
public void doSomething() {
repository.save();
}
}
package mavensnapshot;
import org.springframework.stereotype.Service;
#Service
public class Repository {
public void save() {
System.out.println("production code");
}
}