reset password
Author Message
Victor
Posts: 23
Posted 20:05 Oct 15, 2018 |

Hi Professor.

I just used mockmvc to send a user object without an email (Expecting it to fail) to my registration endpoint.

For some reason, it works. A 200 status code is returned and the response body contains a user object with the email being null.

If I send the same user object to the server without the email (using postman), it returns an error as expected because of my 'nullable = false' constraint on the email property.

What am I doing wrong?

cysun
Posts: 2935
Posted 21:15 Oct 15, 2018 |

The not-null constraint is enforced at the database level so it shouldn't matter how the request is sent in. In the case of Mockmvc, is the user object saved in database? Does the response json contain a generated id?

Victor
Posts: 23
Posted 13:39 Oct 16, 2018 |

When I check the database, nothing is saved.

An ID is generated for the User object in the response body.

Below is the response body:

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /user
       Parameters = {}
          Headers = {Content-Type=[application/json]}
             Body = <no character encoding set>
    Session Attrs = {}

Handler:
             Type = springrest.api.controller.UserController
           Method = public springrest.model.User springrest.api.controller.UserController.addUser(springrest.model.User)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {Content-Type=[application/json;charset=UTF-8]}
     Content type = application/json;charset=UTF-8
             Body = {"id":137,"first_name":"Victor","last_name":"Ahuwanya","position":"STUDENT","email":null,"username":"test_username","enabled":true,"title":"Lawyer","major_or_organizational_unit":"Computer Science","programs":null,"roles":null}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
FAILED: addUserFailure
java.lang.AssertionError: Range for response status value 200 expected:<CLIENT_ERROR> but was:<SUCCESSFUL>
    at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:55)
    at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:82)
    at org.springframework.test.web.servlet.result.StatusResultMatchers.lambda$is4xxClientError$5(StatusResultMatchers.java:92)
    at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:179)
    at springrest.api.controller.UserControllerTest.addUserFailure(UserControllerTest.java:141)
    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.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:124)
    at org.testng.internal.MethodInvocationHelper$1.runTestMethod(MethodInvocationHelper.java:230)
    at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.run(AbstractTestNGSpringContextTests.java:180)
    at org.testng.internal.MethodInvocationHelper.invokeHookable(MethodInvocationHelper.java:242)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:576)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:716)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:988)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
    at org.testng.TestRunner.privateRun(TestRunner.java:648)
    at org.testng.TestRunner.run(TestRunner.java:505)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:455)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:450)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:415)
    at org.testng.SuiteRunner.run(SuiteRunner.java:364)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:84)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1208)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1137)
    at org.testng.TestNG.runSuites(TestNG.java:1049)
    at org.testng.TestNG.run(TestNG.java:1017)
    at org.testng.remote.AbstractRemoteTestNG.run(AbstractRemoteTestNG.java:114)
    at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:251)
    at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:77)


===============================================
    Default test
    Tests run: 1, Failures: 1, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 1, Failures: 1, Skips: 0
===============================================

 

Below is my UserControllerTest code:

@Test
    void addUserFailure() throws Exception {

        User user = new User();
//        user.setEmail("test@test.com");
        user.setFirst_name("Victor");
        user.setLast_name("Ahuwanya");
        user.setMajor_or_organizational_unit("Computer Science");
        user.setPassword("password");
        user.setUsername("test_username");
        Position position = User.Position.STUDENT;
        user.setPosition(position);
        user.setTitle("Lawyer");

        mockMvc.perform( post("/user")
                .contentType(MediaType.APPLICATION_JSON)
                .content(user.toString()))
                .andDo(MockMvcResultHandlers.print())
                .andExpect(status().is4xxClientError());

        System.out.println(user.toString());
    }

cysun
Posts: 2935
Posted 14:25 Oct 16, 2018 |

(a) You should check missing field in your controller code and send appropriate status code and error message. Relying on database constraint check for this purpose is not good because you'll then get a SQL exception, and unless you examine the exception, you cannot tell whether it's caused by some missing fields (i.e. client error and should send back 4xx status codes ) or some server problem (e.g. incorrect SQL syntax and should send back 5xx status codes).

(b) The super class I used for the test examples (and I assume you are using the same) is AbstractTransactionalTestNGSpringContextTests. By default, this class will rollback any changes in the database made by the tests using a transaction rollback. This is usually the desired behavior (you don't want your tests affect your database), but I think a side effect of this is that if a test case is supposed to cause a database constraint violation, you won't be able to get the exception because the transaction is rolled back before the database checks for constraint violation. I think this is probably the reason for the different behaviors of uniting test and manual testing using Postman.

You can change this default behavior by using an annotation @Rollback(false) on the test method. I've added a couple of addUser test cases in my springrest example and one of them has the @Rollback annotation.