I am trying to read a value from a properties file for a unit test case in Spring Boot. I have two config.properties files, one in src/main/resources:
prop = some-value
and one in src/test/resources:
prop = some-test-value
Main Application class:
package company.division.project;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.PropertySource;
#SpringBootApplication(scanBasePackages = "company.division.project")
#PropertySource(value = "classpath:config.properties")
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
System.setProperty("DUMMY_PROPERTY", "dummy-value");
return application.sources(Application.class);
}
public static void main(String[] args) throws Exception {
// Do nothing with main
}
}
Service class to be tested:
package company.division.project.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
#Component
public class Service {
#Autowired
Environment environment;
public String getProperty() {
return environment.getProperty("prop");
}
}
ServiceTest class. I have tried two approaches to retrieving the value in the src/test/resources/config.properties file; one with an #Autowired Environment, and one with an #Value annotation...neither worked:
package company.division.project.service;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.test.context.TestPropertySource;
#RunWith(MockitoJUnitRunner.class)
#TestPropertySource("classpath:config.properties")
public class ServiceTest {
#InjectMocks
Service service;
#Autowired
Environment environment;
#Value("${prop}")
private String expectedProperty;
#Test
public void testGetPropertyWithValueAnnotation() {
assertEquals(expectedProperty, service.getProperty());
}
#Test
public void testGetPropertyWithEnvironment() {
assertEquals(environment.getProperty("prop"), service.getProperty());
}
}
I read somewhere on StackOverflow, that in order to auto-wire components in a Spring test class, I'll need to create an entire context for the test, so I tried this (change the annotations and test runner):
package company.division.project.service;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#SpringBootTest
public class ServiceTest {
#InjectMocks
Service service;
#Autowired
Environment environment;
#Value("${prop}")
private String expectedProperty;
#Test
public void testGetPropertyWithValueAnnotation() {
assertEquals(expectedProperty, service.getProperty());
}
#Test
public void testGetPropertyWithEnvironment() {
assertEquals(environment.getProperty("prop"), service.getProperty());
}
}
The context was created, but both approaches ended in NullPointerExceptions once again.
The problem with your test is that you are trying to use to MockitoJUnitRunner.class in a wrong way.
If you are mocking a Service using #InjectMocks you need to make sure you need to return the value Service.getProperty() by mocking the service call. If you are using SpringRunner.class then you shouldn't have #InjectMocks but should have #Autowired for the service. Following test works.
#RunWith(SpringRunner.class)
#SpringBootTest
public class ServiceTest {
#Autowired
Service service;
#Autowired
Environment environment;
#Value("${prop}")
private String expectedProperty;
#Test
public void testGetPropertyWithValueAnnotation() {
assertEquals(expectedProperty, service.getProperty());
}
#Test
public void testGetPropertyWithEnvironment() {
assertEquals(environment.getProperty("prop"), service.getProperty());
}
}
Thanks to #shazin's answer and some of my own research I've been able to solve the problem.
Basically, there needs to be compatibility between the test runner class specified in #RunWith and the annotations for the Mockito mocks. We want to test the Service class:
Service Class:
#Component
public class Service {
#Autowired
Environment environment;
public String getProperty() {
return environment.getProperty("prop");
}
}
If you're using #RunWith(MockitoJUnitRunner.class), you can use the #InjectMocks and #Mock annotations like below. Whatever is #Autowired in Service will be auto-wired with the mocks:
Test Class with MockitoJUnitRunner:
#RunWith(MockitoJUnitRunner.class)
public class ServiceTest {
#InjectMocks
Service service;
#Mock
Environment mockEnvironment;
#Before
public void before() {
Mockito.when(mockEnvironment.getProperty("prop")).thenReturn("some-test-value")
}
}
But you can't auto-wire anything in the test class itself. That requires a Spring Context (a Spring Context is needed to manage the beans which get auto-wired into objects). That's where #RunWith(SpringRunner.class) comes into the picture. You can use it to run a test case with a dedicated Spring context (you'll notice the test case logs showing a new Spring application being booted up for every test class with the #RunWith(SpringRunner.class) annotation). You'll also need to provide the Configuration details with the #SpringBootTest annotation.
The caveat is that a test class with #RunWith(SpringRunner.class) won't understand the #InjectMocks and #Mock annotations; you'll have to use the #MockBean annotation. This will effectively modify the Spring context by replacing beans with their mocks; anything with the #Autowired annotation will get auto-wired with the mock beans automatically:
Test Class with SpringRunner:
#RunWith(SpringRunner.class)
#SpringBootTest(classes=Application.class)
public class ServiceTest {
#Autowired
Service service;
#MockBean
Environment mockEnvironment;
#Before
public void before() {
Mockito.when(mockEnvironment.getProperty("prop")).thenReturn("some-test-value")
}
}
So...using the #RunWith(SpringRunner.class) didn't achieve anything except change the names of the annotations (#InjectMocks -> #Autowired, and #Mock -> #MockBean), right? Wrong. Using SpringRunner gives you the power of auto-wiring components within your test case. So if you want to use an actual Environment (not a mock one), you can do that as well; just auto-wire it in from the dedicated Spring context:
Test Class with SpringRunner and #Autowired Environment:
#RunWith(SpringRunner.class)
#SpringBootTest(classes=Application.class)
public class ServiceTest {
#Autowired
Service service;
#Autowired
Environment environment;
#Test
public void testServiceGetProperty() {
assertEquals(environment.getProperty("prop"), service.getProperty("prop");
}
}
And that solves the problem.
Related
Through #Autowired i am not able to access the #Component/#Service/#Respository/#Controller class objects in other java files which has #Component annotation (Step 1: Approach) with the Step 1 approach getting Null pointer Exception, but same i could achieve using (Step 2: Approach).
Can anyone please tell me why i am not able to achieve using Step 1 approach:
FYI- I've searched in my entire project i have not used/called/initialized the #Component classes using new method for the autowired class still i getting the issue as "Null Pointer Exception"
Step 1: Using #Autowired Annotation
#Component
public class Processor {
#Autowired
PropertyConfigurator propconfigrator; --> Getting here as null pointer Exception
public void getDetails(){
System.out.println ("Application URL +propconfigrator.getProperties().getProperty("appURL"));
}
}
Step 2: Using ApplicationContext Interface with/without #AutoWired annotation . I am able to get the property value from PropertyConfigurator java file
#Component
public class Processor {
#Autowired
PropertyConfigurator propconfigrator = ApplicationContextHolder.getContext().getBean(PropertyConfigurator.class);
public void getDetails(){
System.out.println ("Application URL +propconfigrator.getProperties().getProperty("appURL"));
}
}
ApplicationContextHolder.java
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
#Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static ApplicationContext getContext() {
return context;
}
}
PropertyConfigurator.java file
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
#Service
#Configurable
public class PropertyConfigurator {
private final Properties properties;
public Properties getProperties () {
return properties;
}
public PropertyConfigurator(){
properties = new Properties();
try {
properties.load(getClass().getClassLoader().getResourceAsStream("dbconfig.properties"));
} catch (IOException e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, e.getMessage(), e);
}
}
}
Why did you use #Configurable annotation? In the code you postet, it doesn't make sense. #Configurable is only needed in cases when instances of this class are not createt by spring.
I have changed into Constructor Injection Autowiring as below with the step 1 approach of above (Not using Step 2. It resolved my issue finally.
Not sure why Spring is not able to inject the bean without using the Constructor Autowiring.
Step 1: Using #Autowired Annotation with Constructor
#Component
public class Processor {
#Autowired
public Processor (PropertyConfigurator propconfigrator) {
this.propconfigrator = propconfigrator;
}
public void getDetails(){
System.out.println ("Application URL +propconfigrator.getProperties().getProperty("appURL"));
}
}
Am new for Junit, any solution for below issue is welcomed.
I have a main class like,
#Service
public class MainClass extends AbstractClass {
#Autowired
ClassA a;
#Autowired
ObjectMapper mapper;
public void methodA(){
....
AnotherClass obj= (AnotherClass)mapper.readerFor(AnotherClass.class).readValue(SOME_CODE);
.......
}
Test Class is,
#RunWith(PowerMockRunner.class)
#PrepareForTest({MainClass.class})
public class MainClassTest {
#Mock
ClassA a;
#Mock
ObjectMapper mapper;
#InjectMocks
MainClass process = new MainClass();
//I have to do somthing for Autowired mapper class of main in test class as well
#Test
public void testProcessRequest() throws Exception{
process.methodA()
}
Am getting null for mapper object in main class while testing, Yes am aware that I haven't dne any kind of initialization.
Is there a better way for writing the junit mapper.
Note : I tried #Mock for ObjectMapper which throws exception at "readerFor".
Thanks in advance.
You do not have to use Mockito/powerMock. Just use a spring boot test.
Something like this:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.fasterxml.jackson.databind.ObjectMapper;
#RunWith(SpringRunner.class)
#SpringBootTest
public class SomeServiceTest {
#Autowired
private SomeService service;
#Autowired
private ObjectMapper om;
#Test
public void try_Me(){
System.out.println(om);
}
}
Adding a some info more to your question.
If you really want to use mockito for the ObjectMapper you should prepare the mock. If not when calling readerFor(...) the mock returns null by default and later, in the readValue method, you are getting a nullpointer.
A basic preparation for the mock might be:
ObjectReader or = Mockito.mock(ObjectReader.class);
Mockito.when(or.readValue(Mockito.anyString())).thenReturn(new instance of your object);
Mockito.when(mapper.readerFor(User.class)).thenReturn(or);
I am trying to execute a JUnit test restful with spring boot application but it is wrong. But if I use a browser it's OK!
Help.
The Repository class:
package com.zhx.help.dao;
import com.zhx.help.model.Girl;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
import java.util.List;
#Repository
#Transactional
public interface GirlRepository extends JpaRepository<Girl,Integer> {
List<Girl> findByAge(Integer age);
}
Controller class
package com.zhx.help.controller;
import com.zhx.help.dao.GirlRepository;
import com.zhx.help.model.Girl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#RestController
public class GirlController {
#Autowired
private GirlRepository girlRepository;
/**查询所有女生列表*/
#GetMapping(value = "/girls")
public List<Girl> girlList(){
return girlRepository.findAll();
}
The JUnit:
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class GirlControllerTest extends MockMvcResultHandlers {
private static Logger log = LoggerFactory.getLogger(GirlControllerTest.class);
//模拟对象
private MockMvc mvc;
#MockBean
private GirlRepository girlRepository;
#Before
public void setUp() {
mvc = MockMvcBuilders.standaloneSetup(new GirlController()).build();
}
#Test
public void girlList() throws Exception {
RequestBuilder request = MockMvcRequestBuilders.get("/girls");
mvc.perform(request).andExpect(status().isOk());
}
The code
https://github.com/longfeizheng/springboot-oracle.git
With your current approach you are basically rendering #SpringBootTest useless. You are loading everything and the first thing you do in the setup is basically discard all the results.
Either use the started context by autowiring the MockMvc or create a simple unit test using Mockito.
Using a preconfigured MockMVC
By simply adding #Autowired on your MockMvc field you should be able to get the prepared instance and you can simply remove your setup method.
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class GirlControllerTest {
private static Logger log = LoggerFactory.getLogger(GirlControllerTest.class);
//模拟对象
#Autowired
private MockMvc mvc;
#MockBean
private GirlRepository girlRepository;
#Test
public void girlList() throws Exception {
RequestBuilder request = MockMvcRequestBuilders.get("/girls");
mvc.perform(request).andExpect(status().isOk());
}
Create a simple Unit test instead of Integration test
Another option is to not load the whole context and simply directly use Mockito and the standaloneSetup you have now.
#RunWith(MockitoJUnitRunner.class)
public class GirlControllerTest {
private static Logger log = LoggerFactory.getLogger(GirlControllerTest.class);
//模拟对象
private MockMvc mvc;
#Mock
private GirlRepository girlRepository;
#InjectMocks
private GirlController girlController;
#Before
public void setUp() {
mvc = MockMvcBuilders.standaloneSetup(girlController).build();
}
#Test
public void girlList() throws Exception {
RequestBuilder request = MockMvcRequestBuilders.get("/girls");
mvc.perform(request).andExpect(status().isOk());
}
Either way will work and which you need/want depends on your needs and what you want (simply test the controller or make it a huge integration test).
Note: You are extending MockMvcResultHandlers don't extend that class just use static imports instead.
You should not create a controller object using new ..
You can do something like below:
#InjectMocks
private GirlController girlController;
mvc = MockMvcBuilders.standaloneSetup(girlController).build();
You can avoid mocking a repository class. Instead create a service class and mock it and access Repository class inside the service class
I made a sample program revealing my question:
package test;
public interface InterfaceA {
}
package test;
public class ClassA implements InterfaceA {
}
package test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class Application {
#Bean
public InterfaceA beanA() {
return new ClassA();
}
#Autowired
private ClassA beanA;
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext
= new AnnotationConfigApplicationContext(Application.class);
}
}
#Autowired doesn't work with the concrete class in this application code.
package test;
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 = Application.class)
public class ApplicationTest {
#Autowired
ClassA beanA;
#Test
public void di() {
System.out.println(beanA);
}
}
But #Autowired works with the concrete class in this test code.
Why is #Autowired working differently in application and test?
I shared the above code at:
https://github.com/izeye/SpringTest
You're using your application context in the main entry point of the application. Make an additional 'Main' class and wire in your context like so:
#Autowired
Application context;
In your test, you're taking the time to start up the application context when you use the annotation: #ContextConfiguration(classes = Application.class).
In your concrete example you're referencing the context in the same class that you're trying to use it in. When you initialize the context in the main, the instance of Application that is running the code has already been instantiated, and did not get autowired, because at the time of its instantiation there wasn't any context for it to use.
It is a bit tricky:
Solution would be to make beanA static method and annotate Application with #Lazy or #DependsOn(value="beanA") annotations. I.e. something like this:
#Configuration
#Lazy
public class Application {
#Bean
public static InterfaceA beanA() {
return new ClassA();
}
#Autowired
private ClassA beanA;
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext
= new AnnotationConfigApplicationContext(Application.class);
}
}
Reason: for actual beanA bean creation spring need to instantiate Application class first. And while trying to resolve reference to a beanA the only information container has about this future bean is its type (InterfaceA) since there is no actual implementation exists.
However in a case of test class context was already initialized and bean can be resolved by actual implementation class which physically was already created. Same would be if you will try to reference beanA by class from any other spring component.
I have a basic Interface which another class is implementing.
package info;
import org.springframework.stereotype.Service;
public interface Student
{
public String getStudentID();
}
`
package info;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
#Service
public class StudentImpl implements Student
{
#Override
public String getStudentID()
{
return "Unimplemented";
}
}
I then have a service to inject that class into
package info;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
#Service
public class InfoService {
#Autowired
Student student;
public String runProg()
{
return student.getStudentID();
}
}
What I want to know is, how do I set up a JUnit test so that a Mock class of the Student interface steps in with a stubbed method instead of the method in StudentImpl. The injection does work but I want to use amock class to simulate the results instead for the sake of testing. Any help would be greatly appreciated.
In my opinion, autowiring in unit tests is a sign that's it's an integration test rather than unit test, so I prefer to do my own "wiring", as you describe. It might require you to do some refactoring of your code, but it shouldn't be a problem. In your case, I would add a constructor to InfoService that get's a Student implementation. If you wish, you can also make this constructor #Autowired, and remove the #Autowired from the student field. Spring would then still be able to autowire it, and it's also more testable.
#Service
public class InfoService {
Student student;
#Autowired
public InfoService(Student student) {
this.student = student;
}
}
Then it will be trivial to pass mocks between your services in your tests:
#Test
public void myTest() {
Student mockStudent = mock(Student.class);
InfoService service = new InfoService(mockStudent);
}