Home > front end >  Can I reuse a captured value in a subsequent mock, with EasyMock?
Can I reuse a captured value in a subsequent mock, with EasyMock?

Time:06-25

I'm trying to do E2E testing (or as close as I can get to E2E) for a Jetty application. I have a neat set-up involving testcontainers and minimal mocking, and everything works in principle, except that I am now having to mock the HTTP workflow that would be handled by Jetty, because I run my tests with JUnit, and all of this because I need to test methods that require authentication -- and yes, I could mock the authentication layer, but I'd rather not for reasons.

Anyway, this is what I'm ending up doing:

String someSegueAnonymousUserId = "9284723987anonymous83924923";

HttpSession httpSession = createNiceMock(HttpSession.class);
// At first, an anonymous user is "created"
expect(httpSession.getAttribute(Constants.ANONYMOUS_USER)).andReturn(null).atLeastOnce();
expect(httpSession.getId()).andReturn(someSegueAnonymousUserId).atLeastOnce();
replay(httpSession);

Capture<Cookie> capturedCookie = Capture.newInstance(); // new Capture<Cookie>(); seems deprecated

// This is the HTTP request for the login step
HttpServletRequest loginRequest = createNiceMock(HttpServletRequest.class);
expect(loginRequest.getSession()).andReturn(httpSession).atLeastOnce();
replay(loginRequest);

// The login process takes the auth cookie and sticks it into the HTTP response
// I capture the cookie because I'm going to need it for subsequent requests, to prove that I'm logged in
HttpServletResponse loginResponse = createNiceMock(HttpServletResponse.class);
loginResponse.addCookie(and(capture(capturedCookie), isA(Cookie.class)));
expectLastCall().atLeastOnce();
replay(loginResponse);

// This is the request for the endpoint I'm going to test
HttpServletRequest createBookingRequest = createNiceMock(HttpServletRequest.class);
// I expect that the endpoint method will check authentication by grabbing the cookies from the request
// Test case fails here: I can't find a way to cast the Object[] returned by the toArray() method to a Cookie[] which is what getCookies() is expected to return.
expect(createBookingRequest.getCookies()).andReturn((Cookie[]) Collections.singletonList(capturedCookie).toArray()).atLeastOnce();
replay(createBookingRequest);

// OK, so, this logs me in, and it works just fine, the cookie is created, and it is valid as far as I can tell
RegisteredUserDTO testUsers = userAccountManager.authenticateWithCredentials(loginRequest, loginResponse, AuthenticationProvider.SEGUE.toString(), "[email protected]", "testpassword", false);
// I don't even get here at all because of that failure above
Response createBookingResponse = eventsFacade.createBookingForMe(createBookingRequest, "someEventId", null);

Now, either there is a way of fixing this, or I'm doing it terribly wrong and I should be doing things very differently. However, I can't find much guidance on the Internet, so my suspicion that I'm doing something that I'm not supposed to do.

Any pointers to how I should do things differently?

CodePudding user response:

This does the job:

expect(createBookingRequest.getCookies()).andReturn(new Cookie[]{capturedCookie.getValue()}).atLeastOnce();

You may encounter following error:

java.lang.IllegalStateException: missing behavior definition for the preceding method call:
HttpServletRequest.getCookies()
Usage is: expect(a.foo()).andXXX()

In this case please take a look at:

java.lang.IllegalStateException: missing behavior definition for the preceding method call getMessage("title")

CodePudding user response:

As I indicated in my comment, as you indicated in your code comments, the problem seems to be related to the following line:

expect(createBookingRequest.getCookies()).andReturn((Cookie[]) Collections.singletonList(capturedCookie).toArray()).atLeastOnce();

which is originating a ClassCastException:

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljavax.servlet.http.Cookie;

Please, try using the Capture class getValue() method to obtain a reference to the captured value, and use that value to provide the necessary return information to the createBookingRequest.getCookies() expectation, something like this:

expect(createBookingRequest.getCookies()).andReturn(new Cookie[] { capturedCookie.getValue() }).atLeastOnce();

Note the use of the getValue() method.

I tested your code to a certain extend with the proposed change:

String someSegueAnonymousUserId = "9284723987anonymous83924923";

HttpSession httpSession = createNiceMock(HttpSession.class);
// At first, an anonymous user is "created"
expect(httpSession.getAttribute("anonymous")).andReturn(null).atLeastOnce();
expect(httpSession.getId()).andReturn(someSegueAnonymousUserId).atLeastOnce();
replay(httpSession);

Capture<Cookie> capturedCookie = Capture.newInstance(); // new Capture<Cookie>(); seems deprecated

// This is the HTTP request for the login step
HttpServletRequest loginRequest = createNiceMock(HttpServletRequest.class);
expect(loginRequest.getSession()).andReturn(httpSession).atLeastOnce();
replay(loginRequest);

// The login process takes the auth cookie and sticks it into the HTTP response
// I capture the cookie because I'm going to need it for subsequent requests, to prove that I'm logged in
HttpServletResponse loginResponse = createNiceMock(HttpServletResponse.class);
loginResponse.addCookie(and(capture(capturedCookie), isA(Cookie.class)));
expectLastCall().atLeastOnce();
replay(loginResponse);

// This is the request for the endpoint I'm going to test
HttpServletRequest createBookingRequest = createNiceMock(HttpServletRequest.class);
// I expect that the endpoint method will check authentication by grabbing the cookies from the request
// Test case fails here: I can't find a way to cast the Object[] returned by the toArray() method to a Cookie[] which is what getCookies() is expected to return.
expect(createBookingRequest.getCookies()).andReturn(new Cookie[] { capturedCookie.getValue() }).atLeastOnce();
replay(createBookingRequest);

// The rest of your code

And the test still complains with the following error message:

java.lang.AssertionError: Nothing captured yet

    at org.easymock.Capture.getValue(Capture.java:101)

It makes perfect sense because the captured value will only be available when the "watched" method, the one we want to capture the arguments for, is invoked.

To solve the new problem, perform the actual addCookie method invocation. For example:

HttpSession httpSession = createNiceMock(HttpSession.class);
// At first, an anonymous user is "created"
expect(httpSession.getAttribute("anonymous")).andReturn(null).atLeastOnce();
expect(httpSession.getId()).andReturn(someSegueAnonymousUserId).atLeastOnce();
replay(httpSession);

Capture<Cookie> capturedCookie = Capture.newInstance(); // new Capture<Cookie>(); seems deprecated

// This is the HTTP request for the login step
HttpServletRequest loginRequest = createNiceMock(HttpServletRequest.class);
expect(loginRequest.getSession()).andReturn(httpSession).atLeastOnce();
replay(loginRequest);

// The login process takes the auth cookie and sticks it into the HTTP response
// I capture the cookie because I'm going to need it for subsequent requests, to prove that I'm logged in
HttpServletResponse loginResponse = createNiceMock(HttpServletResponse.class);
loginResponse.addCookie(and(capture(capturedCookie), isA(Cookie.class)));
expectLastCall().atLeastOnce();
replay(loginResponse);

// Perform the actual addCookie method invocation
Cookie authCookie = new Cookie("jwt", "124adf45...");
loginResponse.addCookie(authCookie);

// This is the request for the endpoint I'm going to test
HttpServletRequest createBookingRequest = createNiceMock(HttpServletRequest.class);
// I expect that the endpoint method will check authentication by grabbing the cookies from the request
// Test case fails here: I can't find a way to cast the Object[] returned by the toArray() method to a Cookie[] which is what getCookies() is expected to return.
expect(createBookingRequest.getCookies()).andReturn(new Cookie[] { capturedCookie.getValue() }).atLeastOnce();
replay(createBookingRequest);

// The rest of your code

Please, consider to see how it fits in the e2e proposed testing approach. Probably a better solution will be reorder your code and only call the capturedCookie.getValue() when the authentication flow is finished, probably after your userAccountManager.authenticateWithCredentials method invocation:

String someSegueAnonymousUserId = "9284723987anonymous83924923";

HttpSession httpSession = createNiceMock(HttpSession.class);
// At first, an anonymous user is "created"
expect(httpSession.getAttribute("anonymous")).andReturn(null).atLeastOnce();
expect(httpSession.getId()).andReturn(someSegueAnonymousUserId).atLeastOnce();
replay(httpSession);

Capture<Cookie> capturedCookie = Capture.newInstance(); // new Capture<Cookie>(); seems deprecated

// This is the HTTP request for the login step
HttpServletRequest loginRequest = createNiceMock(HttpServletRequest.class);
expect(loginRequest.getSession()).andReturn(httpSession).atLeastOnce();
replay(loginRequest);

// The login process takes the auth cookie and sticks it into the HTTP response
// I capture the cookie because I'm going to need it for subsequent requests, to prove that I'm logged in
HttpServletResponse loginResponse = createNiceMock(HttpServletResponse.class);
loginResponse.addCookie(and(capture(capturedCookie), isA(Cookie.class)));
expectLastCall().atLeastOnce();
replay(loginResponse);

// OK, so, this logs me in, and it works just fine, the cookie is created, and it is valid as far as I can tell
// Perform the actual authentication flow: I assume it will call under the hood to the loginResponse.addCookie method
// so everything will be fine when you try gettting the captured method in the next step
RegisteredUserDTO testUsers = userAccountManager.authenticateWithCredentials(loginRequest, loginResponse, AuthenticationProvider.SEGUE.toString(), "[email protected]", "testpassword", false);

// This is the request for the endpoint I'm going to test
HttpServletRequest createBookingRequest = createNiceMock(HttpServletRequest.class);
// I expect that the endpoint method will check authentication by grabbing the cookies from the request
// Test case fails here: I can't find a way to cast the Object[] returned by the toArray() method to a Cookie[] which is what getCookies() is expected to return.
expect(createBookingRequest.getCookies()).andReturn(new Cookie[] { capturedCookie.getValue() }).atLeastOnce();
replay(createBookingRequest);

// I don't even get here at all because of that failure above
Response createBookingResponse = eventsFacade.createBookingForMe(createBookingRequest, "someEventId", null);

I usually use other mocking frameworks but I think the rest of your code looks fine.

  • Related