Test a void method that redirect foward

2019-05-13 23:42发布

问题:

How can I test a void method that redirects me with RequestDispatcher?

What I made until now.

public void testAuthAction_userNull() {
    HttpServletRequest requestMock = createMock(HttpServletRequest.class);
    HttpServletResponse responseMock = createMock(HttpServletResponse.class);
    expect(requestMock.getSession().getAttribute("user")).andReturn(null);
    replay(requestMock);

    AuthAction action = new AuthAction();
    RequestDispatcher rd = requestMock.getRequestDispatcher("/User/login.jsp");
}

the method I want to the test is.

public void execute(HttpServletRequest request, HttpServletResponse response) {
    User user = (User) request.getSession().getAttribute("User");
    try {
        if(user == null) {
            RequestDispatcher rd = request.getRequestDispatcher("/User/login.jsp");
            if(rd != null)
                rd.foward(request, response);
        } else {/* */}
    }
    catch(Exception e){/* */}
}

I'm using JUnit and EasyMock.

回答1:

You need to create a mock of RequestDispatcher expecting to be forwarded, and return it from your mock:

RequestDispatcher dispatcherMock = createMock(RequestDispatcher.class);
expect(requestMock.getRequestDispatcher("/User/login.jsp"))
    .andReturn(dispatcherMock);
// Expect to be forwarded.
dispatcherMock.forward(requestMock, responseMock);
EasyMock.expectLastCall().once();
replay(dispatcherMock);
replay(requestMock);

// Run your test on whatever instance has `execute`:
someInstance.execute(requestMock, responseMock);


回答2:

I will provide a long answer that should be helpful I think.

So, the tested method is this.

public void execute(HttpServletRequest request, HttpServletResponse response) {
    User user = (User) request.getSession().getAttribute("User");
    try {
        if(user == null) {
            RequestDispatcher rd = request.getRequestDispatcher("/User/login.jsp");
            if(rd != null)
                rd.forward(request, response);
        } else {/* */}
    }
    catch(Exception e){/* */}
}

A working test method would be this:

@Test
public void testAuthAction_userNull() {
    HttpServletRequest requestMock = mock(HttpServletRequest.class);
    HttpServletResponse responseMock = mock(HttpServletResponse.class);
    HttpSession sessionMock = mock(HttpSession.class);

    expect(requestMock.getSession()).andReturn(sessionMock);
    expect(sessionMock.getAttribute("User")).andReturn(null);
    expect(requestMock.getRequestDispatcher("/User/login.jsp")).andReturn(null);

    replay(requestMock, sessionMock);

    execute(requestMock, responseMock);

    verify(requestMock, sessionMock);
}

I am using mock() instead of createMock(). It's the same but nicer and shorter.

It returns a null dispatcher because nothing more is needed. I've added a verify() to make sure everything was called as expected.

Then, if you want to make sure the forward is called as well, you also need a mock for the RequestDispatcher.

@Test
public void testAuthAction_userNull() throws Exception {
    HttpServletRequest requestMock = mock(HttpServletRequest.class);
    HttpServletResponse responseMock = mock(HttpServletResponse.class);
    HttpSession sessionMock = mock(HttpSession.class);
    RequestDispatcher rdMock = mock(RequestDispatcher.class);

    expect(requestMock.getSession()).andReturn(sessionMock);
    expect(sessionMock.getAttribute("User")).andReturn(null);
    expect(requestMock.getRequestDispatcher("/User/login.jsp")).andReturn(rdMock);

    rdMock.forward(requestMock, responseMock);

    replay(requestMock, sessionMock, rdMock);

    execute(requestMock, responseMock);

    verify(requestMock, sessionMock, rdMock);
}

The verify() will make sure forward() is called. You do not need an expectLastCall(). It is implicit.

Then to simplify, I would actually do this:

public class MyTest extends EasyMockSupport {
    @Test
    public void testAuthAction_userNull() throws Exception {
        HttpServletRequest requestMock = mock(HttpServletRequest.class);
        HttpServletResponse responseMock = mock(HttpServletResponse.class);
        HttpSession sessionMock = mock(HttpSession.class);
        RequestDispatcher rdMock = mock(RequestDispatcher.class);

        expect(requestMock.getSession()).andReturn(sessionMock);
        expect(sessionMock.getAttribute("User")).andReturn(null);
        expect(requestMock.getRequestDispatcher("/User/login.jsp")).andReturn(rdMock);

        rdMock.forward(requestMock, responseMock);

        replayAll();

        execute(requestMock, responseMock);

        verifyAll();
    }
}

The EasyMockSupport class makes the code simpler.

And to be honest, in this case, when using Spring, I would use spring-test.

@Test
public void testAuthAction_userNull() throws Exception {
    MockHttpServletRequest request = new MockHttpServletRequest();
    MockHttpServletResponse response = new MockHttpServletResponse();

    execute(request, response);

    assertThat(response.getForwardedUrl()).isEqualTo("/User/login.jsp");
}

It does the exact same thing but as you can see it is much shorter because the session and request dispatcher are created under the hood to behave like you would expect.