I am trying to write a Java unit test using Mockito but am having trouble to get a matcher to work.
I want to test the following class
CustomService.java
public class CustomService {
private final ApplicationProperties props;
public CustomService(ApplicationProperties props){
this.props = props;
}
private final ObjectMapper mapper = new ObjectMapper();
public void method(JsonNode message) throws CustomException {
try {
List<String> actions = mapper.readValue(message.get("actions").toString(), mapper.getTypeFactory().constructCollectionType(List.class, String.class));
System.out.println(actions);
} catch (IOException ex){
throw new CustomException(ex);
}
}
}
I have a CustomException class
CustomExcepton.java
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public class CustomException extends Exception {
public CustomException(Throwable cause) {
super(cause);
}
}
I want to test that the CustomException is thrown. I am using Mockito. I have tried the below but the given statement doesn't seem to pick up the (readValue) line of code in the method
CustomServiceTest.java
public class CustomServiceTest {
private final ApplicationProperties props = mock(ApplicationProperties.class);
private final CustomService customService = new CustomService(props);
private static final ObjectMapper objectMapper = new ObjectMapper();
#Test
public void CustomExceptionIsThrown() throws Exception {
ObjectMapper mapper = mock(ObjectMapper.class);
given(mapper.readValue(anyString(), any(TypeReference.class))).willThrow(new IOException("This is a test"));
String json = "{\"actions\":[\"ac1\",\"ac2\",\"ac3\",\"ac4\"]}";
JsonNode d = objectMapper.readTree(json);
assertThrows(CustomException.class, () ->
customService.method(d));
}
}
I get the following error when I run the test
Expected exception.CustomException to be thrown, but nothing was thrown..
Help would be appreciated.
I also met this same problem, and i find that although i mock objectMapper like "
when(objectMapper.readValue(anyString(), any(TypeReference.class))).thenReturn(xxxx);" but when i invoke the test, the first param of readValue(xx,xx) is null which should be a String object. So, you might want to check readValue method input param again.Hope it will help.
There are still some issues with your example code.
You did not inject the Mock of ObjectMapper into your CustomService class.
As your class under test now has a args constructor, you won't be able to inject the mock into your test. You will need to adjust the constructor to pass the ObjectMapper from the outside or rely on Reflections to place the mock manually.
The general advice would be to pass the ObjectMapper as argument into the construtor. Doing it this way, you won't need to change the final on the field mapper.
private final ApplicationProperties props;
private final ObjectMapper mapper;
public CustomService(ApplicationProperties props, ObjectMapper mapper){
this.props = props;
this.mapper = mapper;
}
You still have not defined the behaviour for the mock when the getTypeFactory() method is invoked. You should get a NullPointerException at this point (if your code actually used that mock). In the example below I replaced this definition by using the RETURNS_DEEP_STUBS option,
which causes mapper.getTypeFactory().constructCollectionType(List.class, String.class) to return a mock of CollectionType.
The mock definition with TypeReference is not required, as it does not match your method parameters which are used when the readValue method is invoked. Instead you will have to work with CollectionType.
As your method under test is void you will have to use the willThrow(...).given(...) syntax instead.
Here an example of a working test:
(Only works for a CustomService class with a no-args constructor)
#ExtendWith(MockitoExtension.class)
public class JacksonTest {
static class CustomException extends Exception {
public CustomException(IOException ex) {
super(ex);
}
}
static class CustomService {
private ObjectMapper mapper = new ObjectMapper();
public void method(String c) throws CustomException {
try {
mapper.readValue(c, mapper.getTypeFactory().constructCollectionType(List.class, String.class));
} catch (IOException ex){
throw new CustomException(ex);
}
}
}
#Mock(answer = Answers.RETURNS_DEEP_STUBS)
ObjectMapper mapper;
#InjectMocks
CustomService customService;
#Test
public void testCustomExceptionIsThrown() throws Exception {
BDDMockito.willThrow(new IOException("This is a test")).given(mapper).readValue(ArgumentMatchers.anyString(), ArgumentMatchers.any(CollectionType.class));
Assertions.assertThrows(CustomException.class, () -> customService.method("x"));
}
}
Dependencies:
testCompile "org.junit.jupiter:junit-jupiter-api:5.5.2"
testCompile "org.junit.jupiter:junit-jupiter-engine:5.5.2"
testCompile "org.mockito:mockito-core:3.0.0"
testCompile "org.mockito:mockito-junit-jupiter:3.0.0"
compile "com.fasterxml.jackson.core:jackson-databind:2.9.9"
Related
I have tried with #Autowired on the objectMapper, also tried to Mock it but no success, I just want to use the writeValueAsStringMethod so I do not have to pass a long json string to the content method below.
If I mark my class with #SpringBootTest and also #AutoconfigureMockMvc it works (the objectmapper is not null) but I believe that there must be another way so that it does not become mandatory to use this annotations.
TestClass:
#ExtendWith(MockitoExtension.class)
public class CarControllerTest {
private MockMvc mockMvc;
#InjectMocks
private CarController carController;
#Mock
private ObjectMapper objectMapper;
#MockBean
private CarParts carParts;
#BeforeEach
public void before() {
mockMvc = MockMvcBuilders.standaloneSetup(carController).build();
}
#Test
#DisplayName("Car Controller Test")
public void carControllerTest() {
try {
CarCustomRequest carCustomRequest = buildRequest();
ResultActions resultActions = mockMvc.perform(post("/custom/endpoint")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(carCustomRequest)));
MvcResult mvcResult = resultActions.andExpect(status().isOk()).andReturn();
assertTrue(mvcResult.getResponse().getStatus() == 200);
} catch (Exception e) {
fail("Error testing /custom/endpoint");
}
}
In order to #Autowire you need to load component classes that create the corresponding bean. To make tests faster you could define custom application context and load required beans only inter of using #SpringBootTest without params.
#SpringBootTest(classes = JacksonAutoConfiguration.class)
class ObjectMapperTest {
#Autowired
private ObjectMapper mapper;
#Test
void checkObjectMapper() {
assertNotNull(mapper);
}
}
I would not use #Mock in this case because it will be required to create stubs for required methods.
As an alternative, you could simply create a new instance of the ObjectMapper in test.
class ObjectMapperTest {
private ObjectMapper mapper = new ObjectMapper();
#Test
void checkObjectMapper() {
assertNotNull(mapper);
}
}
You could also register additional modules if required
objectMapper.registerModule(new JavaTimeModule());
I have a spring service (MyService) that uses a mapstruct mapper (CustomMapstructMapper) :
#Service
public class MyService {
private final ClassA classA;
private final ClassB classB;
/*
other private fields declarations...
*/
private CustomMapstructMapper customMapstructMapper = Mappers.getMapper(CustomMapstructMapper.class);
//MyService constructor
public MyService(final ClassA classA, etc...) {
this.classA = classA;
//etc...
}
public ServiceOutput mainMethod(some parameters) {
//some business logic
MyMapperOutput myMapperOutput = customMapstructMapper.map(MapperParameter parameter);
ServiceOutput serviceOutput = some business logic with myMapperOutput;
return serviceOutput;
}
}
I want to unit test MyService (I am using Junit 5) and mock my CustomMapstructMapper output without calling the real mapper during the test execution. I already have another test class that specificly test all the custom mappings in CustomMapstructMapper.
So I have this test class :
#ExtendWith(MockitoExtension.class)
class MyServiceTest {
#InjectMocks
private MyService myService;
#Mock
private CustomMapstructMapper customMapstructMapper;
#Mock
private MyMapperOutput myMapperOutput;
#Test
void testMyService() {
/*
Some mocks creation ..
*/
Mockito.when(myMapperOutput.getSomeField()).thenReturn("Some value");
Mockito.when(customMapstructMapper.map(Mockito.any(MapperParameter.class))).thenReturn(myMapperOutput);
ServiceOutput serviceOutput = myService.mainMethod(some parameters);
/*
Some assertions on serviceOutput
*/
}
}
When I run my test, the implementation of my mapper customMapstructMapperImpl is called in MyService, not my mock.
A NullPointerException is thrown in my mapper because some fields are not initiated.
I have tried to create a mock with the implementation of my mapstruct mapper :
#Mock private CustomMapstructMapperImpl customMapstructMapper;
but I get the same result.
What am I doing wrong?
You're not taking advantage of the spring framework and mapstruct support for it.
in your service change:
private CustomMapstructMapper customMapstructMapper = Mappers.getMapper(CustomMapstructMapper.class);
into
#Autowired
private CustomMapstructMapper customMapstructMapper;
If you don't have it yet at your mapper use
#Mapper( componentModel = "spring" )
This will cause the generated mapper to have the #Component annotation from the spring framework, and it becomes possible to auto-wire this.
After making these changes your method of supplying a mock for testing should work.
The code below works correctly. So maybe something else happened. Your code example is not quite complete.
#ExtendWith(MockitoExtension.class)
public class TestForService {
#InjectMocks
private Service service;
#Mock
private Mapper mapper;
#Test
public void test_service() {
System.out.println("test_service start");
Mockito.when(mapper.doSomeTh()).thenReturn("mock mapper doSomeTh");
String result = service.service();
System.out.println("test_service end, result: " + result);
}
static class Service {
private Mapper mapper = Mapper.getMapper();
public String service() {
System.out.println("Service.service");
return mapper.doSomeTh();
}
public void setMapper(Mapper mapper) {
this.mapper = mapper;
System.out.println("Service.setMapper: " + mapper);
}
}
static class Mapper {
public String doSomeTh() {
System.out.println("Mapper.doSomeTh");
return "mapper end";
}
public static Mapper getMapper() {
System.out.println("Mapper.getMapper");
return null;
}
}
}
result:
Mapper.getMapper
Service.setMapper: mapper
test_service start
Service.service
test_service end, result: mock mapper doSomeTh
A simpler way here:
Just add a package-private setter method into your MyService
// for testing only
void CustomMapstructMapper setCustomMapstructMapper(CustomMapstructMapper mapper) {
this.customMapstructMapper = mapper;
}
and inject your mocked CustomMapstructMapper in test code
#Test
void testMyService() {
/*
Some mocks creation ..
*/
Mockito.when(myMapperOutput.getSomeField()).thenReturn("Some value");
Mockito.when(customMapstructMapper.map(Mockito.any(MapperParameter.class))).thenReturn(myMapperOutput);
myService.setCustomMapstructMapper(customMapstructMapper); // inject your mocked customMapstructMapper here
ServiceOutput serviceOutput = myService.mainMethod(some parameters);
/*
Some assertions on serviceOutput
*/
}
Springboot and Axon: Basically I am unit testing an aggregate that uses three different ObjectMapper instances, each with a different configuration. These are defined in config class :
#Configuration
public class JacksonConfiguration {
#Bean(name="flatMapper")
#Primary
public ObjectMapper flatMapper(){
return new ObjectMapper();
}
#Bean(name="unknownPropertiesMapper")
public ObjectMapper unknownPropertiesMapper(){
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
#Bean(name="nullPropertiesMapper")
public ObjectMapper nullPropertiesMapper(){
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
}
They are injected and used in my aggregate as follow:
#Aggregate
#Data
#Component
public class MyAggregate {
#Autowired
#Qualifier("flatMapper")
private ObjectMapper flatMapper;
#Autowired
#Qualifier("unknownPropertiesMapper")
private ObjectMapper unknownPropertiesMapper;
#Autowired
#Qualifier("nullPropertiesMapper")
private ObjectMapper nullPropertiesMapper;
#AggregateIdentifier
private String id;
//Methods and Handlers: a method is using "unknownPropertiesMapper" is "changedKeySet"
when I run SpringBootApplication everything is properly instanciated and working as expected, but when testing I get NullPointerException over thier instances:
#ContextConfiguration(classes = JacksonConfiguration.class)
#SpringBootTest(classes = OccurrenceAggregate.class)
#ComponentScan(basePackageClasses = MyAggregate.class)
public class AggregateTest {
private FixtureConfiguration<MyAggregate> fixture;
#BeforeEach
public void setUp() {
fixture = new AggregateTestFixture<>(MyAggregate.class);
}
#Test
public void myTest() {
fixture.givenNoPriorActivity()....
}
test console:
org.axonframework.test.AxonAssertionError: The command handler threw an unexpected exception
Expected <ANYTHING> but got <exception of type [NullPointerException]>. Stack trace follows:
java.lang.NullPointerException
at com.example.business.aggregates.MyAggregate.changedKeySet(MyAggregate.java:185)
changedKeySet() is throwing NPE because its using unknownPropertiesMapper and it is value is null.
as I mentionned it works fine when I run the Main class but not in tests (Junit5).
The Aggregate is not set up correctly. The correct way to inject a Spring Bean into the Aggregate is to add it to the CommandHandler method.
#CommandHandler
public void handle(ACommand cmd, ObjectMapper flatMapper) {
In the test fixture you can inject it this way:
fixture.registerInjectableResource(flatMapper);
Is it possible somehow to reuse Jackson ObjectMapper reference in my test classes when I testing my rest services using Jersey test framework.
I registered JacksonFeature in my abstract class from which my test classes extends.
public abstract class AbstractRestIntegrationTest extends JerseyTest {
#Override
protected Application configure() {
ResourceConfig resourceConfig = new ResourceConfig(getResourceClass());
resourceConfig.register(JacksonFeature.class);
resourceConfig.register(MultiPartFeature.class);
return resourceConfig;
}
}
EDIT
In my test class I calling the rest service, which returns to me json. Then I want to parse this json by jackson and check if it contains objects. And my question is if I can reuse that ObjectMapper in test method from Jersey when I register JacksonFeature.
public class ManagedElementIntegrationTest extends AbstractRestIntegrationTest {
#Override
protected Class<?> getResourceClass() {
return ManagedElementRestService.class;
}
#Test
public void testGetManagedElementById() throws IOException {
IManagedElementService managedElementService = getBeanOfClass(IManagedElementService.class);
ManagedElement me = prepareManagedElementObject();
try {
when(managedElementService.getUpdatedApplication(anyString())).thenReturn(me);
} catch (ManagedElementNotFoundException e) {
fail("Exception not expected: " + e);
}
String response = target("me/app").request().accept(MediaType.APPLICATION_JSON).get(String.class);
ObjectMapper mapper = new ObjectMapper();
JsonNode actualObj = mapper.readTree(response);
assertNotNull(actualObj);
assertNotNull(actualObj.get("mapPosition"));
assertNull(actualObj.get("alarms"));
// code continues below.
}
}
Am new to spring and spring boot. I was actually trying to a restclient in springboot. When i write the client and get the response, i wanted to read the response body which is String and wanted to convert that to JSON for my use. So i have written RestClient class and from that I have autowired JsonUtil class which does String to JSON. But my autowired jsonutil is not available for me to use in Rest client class. i dont know what i need to do here. Below is my code.
My RestClient
#Component
public class RestClient {
#Autowired
JsonUtil jsonUtil;
private static final String URL ="https://test.com?q=";
private static String getURL(String value){
if(!StringUtils.isBlank(value))
return URL+value;
return null;
}
private static void get(String val){
RestTemplate restTemplate = new RestTemplate();
String resourceUrl=getURL(val);
ResponseEntity<String> response = null;
if(!StringUtils.isBlank(resourceUrl)){
response = restTemplate.getForEntity(resourceUrl , String.class);
}
//Though i have autowired JsonUtil, i dont have that object to use it here
jsonUtil. //this variable is not available
}
My JsonUtil
import com.fasterxml.jackson.databind.ObjectMapper;
#Component
public class JsonUtil {
#Autowired
private ObjectMapper objectMapper;
public JsonUtil(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
public JsonNode getStringAsJson(String value) {
try {
return objectMapper.readTree(value);
}catch (IOException e) {
String msg = e.getMessage();
LOG.info(msg);
}
return null;
}
}
Any help appreciated
You are trying to use an instance variable inside a static function. That is not possible. Think of it this way, there can be numerous instances of the class with each having its own particular instance variable. How will the static method know which one to pick.
You should make the methods non static to make it work.
However, if you are determined to make the function static make the variable also static and since you can't autowire static fields, try something like this
private static JsonUtil jsonUtil;
#Autowired
ApplicationContext ctx;
#PostConstruct
public void init() {
jsonUtil = ctx.getBean(JsonUtil.class);
}