No value at JSON path "$.name", exception: json can not be null or empty, Using Mockmvc and Spring-boot

11,145

Ok I checked your github and the problem is that you are mocking your KingdomService:

@MockBean
private KingdomService kingdomServiceMock;

But in these failing tests you are not asserting any behaviour for the mocked methods that are called. The default response for a mocked class is to return null hence the kingdomService.getKingdom(name) method here returns null always:

@RequestMapping("/Westeros/{name}")
    public Kingdom getKingdom(@PathVariable String name) {
        return kingdomService.getKingdom(name);
}

You probably want to do more of an integration test in which case I would say you don't want to mock the service.

Therefore in your get tests that are currently working instead of setting up a mock behavior you should autowire the repository and actually add the Kingdoms you want to test for, e.g.:

@Autowired
KingdomRepository kingdomRepository;

@Test
@Transactional
    public void getAllKingdomsTest() throws Exception {

        List<Kingdom> kingdoms = setUpAListOfKingdoms();

        kingdomRepository.saveAll(kingdoms);
        kingdomRepository.flush();

        mockMvc.perform(get("/Westeros").accept(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$[0].name").value("TheNorth"))
                .andExpect(jsonPath("$[1].name").value("TheRiverlands"));
    }

The @Transactional annotation ensures that the interactions you have with the database are rolled back at the end of each test.

EDIT: You should also make sure your repositories are implementing JpaRepository not PagingAndSortingRepository. This way you can call the .flush() method on your repo to ensure all pending changes to the DB are instantly flushed to it.

Share:
11,145
Darwin
Author by

Darwin

Updated on June 04, 2022

Comments

  • Darwin
    Darwin almost 2 years

    In the spirit of learning Spring-boot with Spring Mock-Mvc and/or Mockito, I have build a small API with the potential of getting complicated later when I learn more stuff.

    The Theme is "A Song of Ice and Fire" or "Game Of Thrones". So far I only have one package where you can add, delete and get different kingdoms when you send requests to "/Westeros". Each Kingdom only require a name property for now.

    The Database I am using is Neo4J.

    I uploaded the code to github, here is the link https://github.com/darwin757/IceAndFire

    The Problem: The Porblem is in my KingdomControllerTest class in the methods addKingdomTest and updateKingdomTest

        @Test
    public void addKingdomTest() throws Exception {
    
        mockMvc.perform(post("/Westeros").contentType(MediaType.APPLICATION_JSON_UTF8).content("{\"name\":\"Dorne\"}")
                .accept(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isCreated()).andReturn();
    
        //This part of the test is not working
        mockMvc.perform(get("/Westeros/Dorne").contentType(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("Dorne"));
    
    }
    
    @Test
    public void updateKingdomTest() throws Exception {
    
        mockMvc.perform(post("/Westeros").contentType(MediaType.APPLICATION_JSON_UTF8).content("{\"name\":\"Dorne\"}")
                .accept(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isCreated());
    
        mockMvc.perform(put("/Westeros/Dorne").contentType(MediaType.APPLICATION_JSON_UTF8)
                .content("{\"name\":\"theReach\"}").accept(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isOk());
    
        //This Part of the test is not working
        mockMvc.perform(get("/Westeros/Dorne").contentType(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("the Reach"));
    
    }
    

    As you see, when I ask the API to create a new Kingdom, it returns a 201 isCreated or 200 isOK, but when I send a get request I get back a "No value at JSON path exception"

    java.lang.AssertionError: No value at JSON path "$.name", exception: json can not be null or empty
    at org.springframework.test.util.JsonPathExpectationsHelper.evaluateJsonPath(JsonPathExpectationsHelper.java:245)
    at org.springframework.test.util.JsonPathExpectationsHelper.assertValue(JsonPathExpectationsHelper.java:99)
    at org.springframework.test.web.servlet.result.JsonPathResultMatchers$2.match(JsonPathResultMatchers.java:100)
    at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171)
    at com.example.Westeros.Kingdoms.KingdomControllerTest.addKingdomTest(KingdomControllerTest.java:95)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
    

    I am Very new to Spring in general, and I did not find proper guides on spring Mock-Mvc or Mockito. I have no idea what is wrong, is it my syntax or my API? any help would be appreciated.

    Here is the entire Class:

    package com.example.Westeros.Kingdoms;
    
    import org.junit.Before;
    import org.junit.Ignore;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.stubbing.OngoingStubbing;
    import org.springframework.beans.factory.annotation.Autowired;
    import 
    org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
    import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.mock.mockito.MockBean;
    import org.springframework.http.MediaType;
    import org.springframework.test.context.junit4.SpringRunner;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.MvcResult;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    
    import static org.mockito.Mockito.when;
    import static org.junit.Assert.*;
    import static org.mockito.Mockito.any;
    
    import static 
    org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    import static 
    org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
    import static 
    org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
    import static 
    org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
    import static 
    org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    import static 
    org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
    import static 
    org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
    import static 
    org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
    import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
    
    import java.util.ArrayList;
    import java.util.List;
    import org.junit.Assert;
    
    //TODO Major refactor required to clean up this class and consider the 
    testing strategy 
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class KingdomControllerTest {
    
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mockMvc;
    
    @MockBean
    private KingdomService kingdomServiceMock;
    
    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
    }
    
    @Test
    public void getAllKingdomsTest() throws Exception {
    
        List<Kingdom> kingdoms = setUpAListOfKingdoms();
    
        when(kingdomServiceMock.getAllKingdoms()).thenReturn(kingdoms);
    
        mockMvc.perform(get("/Westeros").accept(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$[0].name").value("TheNorth"))
                .andExpect(jsonPath("$[1].name").value("TheRiverlands"));
    }
    
    
    @Test
    public void getKingdomTest() throws Exception {
    
        Kingdom theNorth = setUpAKingdom("TheNorth");
        kingdomServiceMock.addKingdom(theNorth);
    
        when(kingdomServiceMock.getKingdom("TheNorth")).thenReturn(theNorth);
    
        mockMvc.perform(get("/Westeros/TheNorth")).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$.name").value("TheNorth"));
    }
    
    // FIXME This test is returning 201 isCreated,
    // but if I perform a get after I get an assertion exception that the variable
    // name is empty.
    @Test
    public void addKingdomTest() throws Exception {
    
        mockMvc.perform(post("/Westeros").contentType(MediaType.APPLICATION_JSON_UTF8).content("{\"name\":\"Dorne\"}")
                .accept(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isCreated()).andReturn();
    
        //This part of the test is not working
        mockMvc.perform(get("/Westeros/Dorne").contentType(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("Dorne"));
    
    }
    
    @Test
    public void updateKingdomTest() throws Exception {
    
        mockMvc.perform(post("/Westeros").contentType(MediaType.APPLICATION_JSON_UTF8).content("{\"name\":\"Dorne\"}")
                .accept(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isCreated());
    
        mockMvc.perform(put("/Westeros/Dorne").contentType(MediaType.APPLICATION_JSON_UTF8)
                .content("{\"name\":\"theReach\"}").accept(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isOk());
    
        //This Part of the test is not working
        mockMvc.perform(get("/Westeros/Dorne").contentType(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("the Reach"));
    
    }
    
    @Test
    public void deleteKingdomTest() throws Exception {
    
        mockMvc.perform(post("/Westeros").contentType(MediaType.APPLICATION_JSON_UTF8).content("{\"name\":\"theVale\"}")
                .accept(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isCreated());
    
        mockMvc.perform(delete("Westeros/theVale").contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isNotFound());
    
    }
    
    // FIXME refer to the KingdomController class, the method should be moved to
    // another class more suited to it's purpose
    @Test
    public void deleteAlltest() {
    }
    
    private List<Kingdom> setUpAListOfKingdoms() {
    
        Kingdom theNorth = setUpAKingdom("TheNorth");
        Kingdom theRiverlands = setUpAKingdom("TheRiverlands");
    
        List<Kingdom> kingdoms = new ArrayList<Kingdom>();
    
        kingdoms.add(theNorth);
        kingdoms.add(theRiverlands);
    
        // FIXME wrong place for this code but I can't find another
        kingdomServiceMock.addKingdom(theNorth);
        kingdomServiceMock.addKingdom(theRiverlands);
    
        return kingdoms;
    
    }
    
    private Kingdom setUpAKingdom(String name) {
    
        Kingdom kingdom = new Kingdom(name);
        return kingdom;
    }
    

    }

    Thank you in advance.

    • Plog
      Plog over 6 years
      I would usually split testing my gets from my posts and puts by accessing the repository directly in the tests instead of creating test data through the mockMvc. I don't think this will fix your issue though. The failings might indicate that your get just isn't working. Have you tried it outside of tests?
    • mrkernelpanic
      mrkernelpanic over 6 years
      I dont know your response body, but jsonPath("$.name") is wrong, either change to jsonPath("name") or show your response entity
    • Darwin
      Darwin over 6 years
      @Plog I do have a test only for the get method, and a test for the getAll method, and they work fine. I just did not want to test the Post method by only checking for a 201 response that is why I added another get test there to make sure that the object I intended is actually created.
    • mrkernelpanic
      mrkernelpanic over 6 years
      Ah I now see that you want to return a List of Kingdom. So you can assert one property of one item like this: jsonPath("kingdomList.[0].name") i.e. I would create a wrapper class called e.g. KingdomList which contains the List<Kingdom>
  • Darwin
    Darwin over 6 years
    Thank you good sir for taking the time to answer. Unfortunately, using the an Autowired repository broke my get tests and did not fixed my post tests. The reason I am using Mock is that I wanted to learn how to test a framework with Mockmvc instead of using direct JUnit with OkHttp for example, and all my tests should run in isolation on a clean database, and not affect one another. I think the my main question will be, Is there a way with MockMvc to test that the element I create through my POST method actually has the right data? or should I just be happy with the 201 response code?
  • Plog
    Plog over 6 years
    What broke exactly when using your autowired repository? It should work. Also the MockMvc approach doesn't mean you need to mock your services. It just provides a way of mocking the Spring MVC infrastructure including mocking HTTP requests and responses. If anything I wouldn't use mocks in this case since tests using MockMvc are already more of a full integration test for your HTTP endpoints. You can make you tests run in isolation and not affect each other in the DB by marking your tests as @Transactional. I've updated my question to include that.
  • Plog
    Plog over 6 years
    And as for testing whether your post creates the right data. Yes, you can test this by calling the post via mockMvc then calling the get directly on your repository to check the DB has the entity you just created.
  • Plog
    Plog over 6 years
    @Darwin I also realised you probably should be using JpaRepository as your repositories instead of PagingAndSortingRepository. This way you can call the flush() method which you can call in your tests to ensure all requests to change the DB are flushed immediately. This should help fix things too. Editted answer again.
  • Darwin
    Darwin over 6 years
    Thanks for your continuos support, I understand a lot more about the problem now, when I change my KingdomRepository to implement JpaRepository I get a java.lang.IllegalStateException: Failed to load ApplicationContext (I did include all the necessary maven dependencies and imports) The whole thing has it's own git branch now. I am just checking for 201/200 response in the controller tests for now.