Hot questions for Using Mockito in jackson

Question:

I'm trying to create a JsonProcessingException to be thrown by a mock object.

when(mapper.writeValueAsString(any(Object.class))).thenThrow(new JsonProcessingException("Error"));

However I'm unable to create a JsonProcessingException object as all the constructors are protected. How do I get around this?


Answer:

how about you create an anonymous exception of type JsonProcessingException

when(mapper.writeValueAsString(any(Object.class))).thenThrow(new JsonProcessingException("Error"){});

The {} braces does the trick. This is much better since it is not confusing to the reader of the test code.

Question:

I have a Jersey endpoint that I am trying to test.

Endpoint:

@Path("image")
class ImageResource {

    @Inject
    private ImageService imageService;

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response save(ImageVO imageVO) {
        imageVO = imageService.save(imageVO);
        ResponseVO responseVO = new ResponseVO(true, imageVO);
        return Response.status(Response.Status.CREATED).entity(responseVO).build();
    }
}

Test:

class ImageResourceTest extends JerseyTest {

    private static final String BASE_ENDPOINT = "rest";
    private static final String IMAGE_ENDPOINT = "image";

    @Mock
    private ImageService imageService;

    @InjectMocks
    private ImageResource imageResource;

    @Override
    protected URI getBaseUri() {
        return UriBuilder.fromUri(super.getBaseUri()).path(BASE_ENDPOINT).build();
    }

    @Override
    protected Application configure() {
        MockitoAnnotations.initMocks(this);

        ResourceConfig config = new ResourceConfig();
        config.register(imageResource);
        return config;
    }

    @Test
    public void testSave() {
        ImageVO imageVO = new ImageVO();
        imageVO.setId("TEST");
        when(imageService.save(imageVO)).thenReturn(imageVO);

        Entity e = Entity.entity(imageVO, MediaType.APPLICATION_JSON);
        Response response = target().path(IMAGE_ENDPOINT).request().post(e, Response.class);
        assertEquals(201, response.getStatus());

        ResponseVO responseVO = response.readEntity(ResponseVO.class);
        assertTrue(responseVO.isSuccess());
    }
}

And the exception:

Jul 13, 2015 9:14:28 AM org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory$GrizzlyTestContainer start
INFO: Starting GrizzlyTestContainer...
Jul 13, 2015 9:14:28 AM org.glassfish.grizzly.http.server.NetworkListener start
INFO: Started listener bound to [localhost:9998]
Jul 13, 2015 9:14:28 AM org.glassfish.grizzly.http.server.HttpServer start
INFO: [HttpServer] Started.
Jul 13, 2015 9:14:28 AM org.glassfish.jersey.server.ServerRuntime$Responder process
SEVERE: Error occurred when processing a response created from an already mapped exception.
InboundJaxrsResponse{ClientResponse{method=POST, uri=http://localhost:9998/rest/image, status=500, reason=Request failed.}}
Jul 13, 2015 9:14:28 AM org.glassfish.jersey.server.ServerRuntime$Responder release
WARNING: Attempt to release request processing resources has failed for a request.
java.lang.IllegalStateException: Illegal attempt to call getOutputStream() after getWriter() has already been called.
    at org.glassfish.grizzly.http.server.Response.getNIOOutputStream(Response.java:621)
    at org.glassfish.grizzly.http.server.Response.getOutputStream(Response.java:646)
    at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer$ResponseWriter.writeResponseStatusAndHeaders(GrizzlyHttpContainer.java:265)
    at org.glassfish.jersey.server.ServerRuntime$Responder$1.getOutputStream(ServerRuntime.java:561)
    at org.glassfish.jersey.message.internal.CommittingOutputStream.commitStream(CommittingOutputStream.java:200)
    at org.glassfish.jersey.message.internal.CommittingOutputStream.flushBuffer(CommittingOutputStream.java:305)
    at org.glassfish.jersey.message.internal.CommittingOutputStream.commit(CommittingOutputStream.java:261)
    at org.glassfish.jersey.message.internal.CommittingOutputStream.close(CommittingOutputStream.java:276)
    at org.glassfish.jersey.message.internal.OutboundMessageContext.close(OutboundMessageContext.java:835)
    at org.glassfish.jersey.server.ContainerResponse.close(ContainerResponse.java:411)
    at org.glassfish.jersey.server.ServerRuntime$Responder.release(ServerRuntime.java:667)
    at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:438)
    at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:265)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:319)
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:236)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1028)
    at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer.service(GrizzlyHttpContainer.java:363)
    at org.glassfish.grizzly.http.server.HttpHandler$1.run(HttpHandler.java:217)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:565)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:545)
    at java.lang.Thread.run(Thread.java:619)
Jul 13, 2015 9:14:28 AM org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory$GrizzlyTestContainer stop
INFO: Stopping GrizzlyTestContainer...
Jul 13, 2015 9:14:28 AM org.glassfish.grizzly.http.server.NetworkListener shutdownNow
INFO: Stopped listener bound to [localhost:9998]

I have been beating my head against the wall with this for a couple of days, but can't find out the solution. I have multiple other endpoints that I test the same way, that do not have this issue. I'm using Jersey 2.6 with Java 6u5.


Answer:

I don't know, I couldn't reproduce the exact problem, with what you have provided (though I did get some errors). I'll just post what I tested with, and maybe you can figure it out.

The major changes I made were:

  • Just used Jersey's injection framework to handle the DI. You can see it where I config.register(new AbstractBinder() below. See more about it here
  • Moved the when of the Mock to the point of configuration, instead of in the test method.

If you still can't get it working, try to create a new project with just the dependencies I've provided, and the one test case. If it works, then it's something you're not showing us, that may be the problem. If you want to provide a small Github project that reproduces the problem, I could have a look at it.

Maven Dependencies

<dependencies>
    <dependency>
        <groupId>org.glassfish.jersey.test-framework.providers</groupId>
        <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
        <version>2.6</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.jaxrs</groupId>
        <artifactId>jackson-jaxrs-json-provider</artifactId>
        <version>2.4.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-all</artifactId>
        <version>1.9.0</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.10</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Test case

import java.net.URI;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;

import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static org.junit.Assert.*;
import org.mockito.Mockito;
import static org.mockito.Mockito.when;

public class ImageServiceTest extends JerseyTest {

    private static final String BASE_ENDPOINT = "rest";
    private static final String IMAGE_ENDPOINT = "image";
    private static final ImageVO imageVO = new ImageVO("Test");

    public static interface ImageService {
        ImageVO save(ImageVO imageVo);
    }

    public static class ImageVO {
        public String id;
        public ImageVO() {}
        public ImageVO(String id) { this.id = id; }
    }

    public static class ResponseVO {
        public boolean cool;
        public ImageVO imageVo;
        public ResponseVO() {}
        public ResponseVO(boolean cool, ImageVO imageVo) {
            this.cool = cool; this.imageVo = imageVo;
        }
    }

    @Path("image")
    public static class ImageResource {

        @Inject
        private ImageService imageService;

        @POST
        @Produces(MediaType.APPLICATION_JSON)
        @Consumes(MediaType.APPLICATION_JSON)
        public Response save(ImageVO imageVO) {
            imageVO = imageService.save(imageVO);
            ResponseVO responseVO = new ResponseVO(true, imageVO);
            return Response.status(Response.Status.CREATED).entity(responseVO).build();
        }
    }

    @Override
    public Application configure() {

        final ImageService imageService = Mockito.mock(ImageService.class);
        when(imageService.save(imageVO)).thenReturn(imageVO);

        ResourceConfig config = new ResourceConfig();
        config.register(JacksonJsonProvider.class);
        config.register(ImageResource.class);
        config.register(new AbstractBinder(){
            @Override
            protected void configure() {
                bind(imageService).to(ImageService.class);
            }
        });     
        return config;
    }

    @Override
    protected URI getBaseUri() {
        return UriBuilder.fromUri(super.getBaseUri()).path(BASE_ENDPOINT).build();
    }

    @Test
    public void testSave() {
        Entity e = Entity.entity(imageVO, MediaType.APPLICATION_JSON);
        Response response = target().path(IMAGE_ENDPOINT).request().post(e, Response.class);
        assertEquals(201, response.getStatus());

        ResponseVO responseVO = response.readEntity(ResponseVO.class);
        assertTrue(responseVO.cool);
        response.close();
    }
}

Question:

I'm attempting to stub the "isObject" method of a mock JsonNode object. The method returns a boolean. It internally calls the "getNodeType" method, which returns a JsonNodeType, and checks that value against JsonNodeType.OBJECT. The program compiles, and then throws an exception at runtime saying that I'm attempting to stub getNodeType with a boolean even though the code attempts to stub isObject.

My questions are: Why is mockito trying to stub a different method than the one specified in the code? How do I get mockito to stub the correct method?

I've isolated the problem, as far as I can tell, to it's most basic elements. The following code throws an exception at line beginning with "when(":

import static org.mockito.Mockito.*;
import com.fasterxml.jackson.databind.JsonNode;

public class Toy {

    public static void main(String[] args) {
        JsonNode testNode = mock(JsonNode.class);
        when(testNode.isObject()).thenReturn(true);
    }
}

The exception message is this:

Exception in thread "main" org.mockito.exceptions.misusing.WrongTypeOfReturnValue: 
Boolean cannot be returned by getNodeType()
getNodeType() should return JsonNodeType

If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:

1. This exception *might* occur in wrongly written multi-threaded tests.
   Please refer to Mockito FAQ on limitations of concurrency testing.
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.

For reference, here is the implementation of the "isObject" method in the jackson code:

public final boolean isObject() {
    return getNodeType() == JsonNodeType.OBJECT;
}

I'm using Jackson 2.3.3 and Mockito 1.10.19. I'm completely flummoxed by the behavior of this code. Any help is greatly appreciated.


Answer:

You cannot mock final methods with Mockito. But you can use PowerMockito. Look at this answer.

Question:

I have problem with testing rest controller endpoint. I'm trying to POST entity to endpoint that is annotated with @Valid on @RequestBody field. Entity fields are annotated with validation rules and jackson annotations.

I'm trying to test that POST endpoint with unit tests.

Entity and field with annotations

public class User {
    //other fields 

    @Column(name = "PASSWORD", nullable = false)
    @NotEmpty(message = "Users password must be filled.")
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private String userPassword;
}

Controller

@RestController
@RequestMapping(value = "/api/users", produces = "application/hal+json")
public class UserController {

    @PostMapping("")
    public ResponseEntity<User> addNewUser(@Valid @RequestBody User newUser) {
        User createdUser = userService.saveUserInDatabase(newUser);
        return new ResponseEntity<>(createdUser, HttpStatus.OK);
    }
}

And unit test

@RunWith(MockitoJUnitRunner.class)
public class UserControllerTest {
    //other tests 
    @Test
    public void shouldBeAbleToAddNewUser() throws Exception {
        User newUser = new User();
        newUser.setUserName("userName");
        newUser.setUserEmail("userName@domain.com");
        newUser.setUserPassword("secret1");
        newUser.setEnable(true);

        User createdUser = new User(1L, "userName", "userName@domain.com", "secret1", true);

        when(userService.saveUserInDatabase(any(User.class))).thenReturn(createdUser);

        mockMvc.perform(post("/api/users")
                    .contentType(MediaTypes.HAL_JSON_UTF8)
                    .content(jsonUser.write(newUser).getJson()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.userName", Matchers.equalToIgnoringCase(createdUser.getUserName())))
                .andExpect(jsonPath("$.userId", Matchers.is(createdUser.getUserId().intValue())))
                .andExpect(jsonPath("$.userPassword").doesNotExist());
    }
}

What is important. When entity is fetched (GET) with swagger, field "userPassword" is not serialized. It works as expected thanks to @JsonProperty annotation. When entity is POSTed with swagger is also works correctly, password is saved in database and response doesn't contain "userPassword" field. Also, if any required (annotated with @NotEmpty) field is missing, exception is thrown.

Tests that are testing fetching data, works correctly, 'userPassword' is not visible response in json

andExpect(jsonPath("$.userPassword").doesNotExist())

But when attached test is executed, it fails with wrong status (400 instead 200) and information "Users password must be filled." Password is filled but it looks like it is not pushed to request body. I did some debug and notice that when @JsonProperty is added to "userPassword" field, that field is set to "null".

//edit jsonUser used to post new User data in JSON format is defined this way

@RunWith(MockitoJUnitRunner.class)
public class UserControllerTest {

//other variables and methods

private JacksonTester<User> jsonUser;

    @Before
    public void setup() {
        JacksonTester.initFields(this, new ObjectMapper());
        mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
    }
}

//edit I've found solution for my problem. jsonUser.write to work must first read data from newUser (so obviously) and include json annotation. As getter is blocked by jsonproperty it is omitted during read. That is why test case din't work but POST through swagger works correctly. To solve it, I've prepared String that contain data in json format

String userDetails = "{\"userName\":\"userName\",\"userEmail\":\"userName@domain.com\",\"userPassword\":\"secret1\",\"enable\":true}";

mockMvc.perform(post("/api/users")
                    .contentType(MediaTypes.HAL_JSON_UTF8)
                    .content(userDetails)) 
                    .andExpect(status().isOk());

Answer:

You set the content of the post with

jsonUser.write(newUser).getJson()

jsonUser is a JacksonTester and is given a Jackson ObjectMapper to perform serialization / deserialization. As you are using the write method to serialize your user object it will respect the

@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)

The confusion maybe the use of 'write'. The method on the JacksonTester is write() which means serialize (write the object to json) where as the JsonProperty.Access.WRITE_ONLY means only use this field during deserialize (write from the json to the object)

And the Json you get will not include any password details. i.e. you get something like:

{"userName":"userName","userEmail":"userName@domain.com","enable":true}

You use this as the content for your test POST request. When Jackson comes to deserialize your json from the content of the POST request it won't find a password. This is why you see what you see.

I understand why you're using the WRITE_ONLY option on your class, it makes sense to prevent the password being written out when it is serialized. However, that means you can't use that class to create the json that you want to send up to the server by simply serializing it.

One approach would be to subclass your User with a TestUser which just had a password field. Then use TestUser in your JacksonTester.

public class TestUser extends User {
    private String userPassword;
}

Then in your test class you'd replace User with TestUser

private JacksonTester<TestUser> jsonUser;

and

    TestUser newUser = new TestUser();
    newUser.setUserName("userName");
    newUser.setUserEmail("userName@domain.com");
    newUser.setUserPassword("secret1");
    newUser.setEnable(true);