Can't find JSTL tag library when running test

2019-02-19 10:43发布

问题:

I have a Servlet-based application (OAuth implementation) which delegates some of the response rendering to JSP, as in the following example:

private void doLoginPage( AuthorizationSession authzSession, String errorMsg, HttpServletRequest request, HttpServletResponse response ) throws OAuthSystemException {
    try {
        response.setHeader( HTTP.CONTENT_TYPE, ContentType.create( "text/html", "utf-8" ).toString() );
        request.getRequestDispatcher( "/WEB-INF/OAuthLogin.jsp" ).include( request, response );
    } catch ( Throwable e ) {
        throw new OAuthSystemException( "Error generating login page", e );
    }
}

Here's the JSP file (simplified):

<%@ page contentType="text/html;charset=UTF-8" language="java" session="false" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<jsp:useBean id="errorMsg" scope="request" class="java.lang.String"/>

<html>
<head>
<title>Sign In</title>
</head>
<body style="text-align: center;">
<h1>Sign In</h1>
<div style="border: 1px black;">
    <c:if test="${!(empty errorMsg)}">
        <p style="color:red;">${errorMsg}</p>
    </c:if>
    <form method="post" action="<c:url value="/authorize"/>">
        <div><label for="email">Email:</label></div>
        <div><input type="text" name="email" id="email"/></div>
        <div><label for="password">Password:</label></div>
        <div><input type="password" name="password" id="password"/></div>
        <div><input type="submit" title="Sign In" /></div>
    </form>
</div>
</body>
</html>

I've got my unit testing set up so that I can run server-based tests as unit tests, with injected mocks, using Jetty as an in-process server (OK, so it's not pure unit testing).

As part of my unit testing, I wrote a test to ensure that the page is being rendered when appropriate, and that it includes certain key properties (I'm using TestNG and Hamcrest):

@Test
public void testRequestGrantYieldsLoginPage() throws Exception {
    HttpGet request = new HttpGet( String.format( "%s/authorize?client_id=%s&redirect_uri=%s&response_type=token",
            serverConnector.getServerURL(),
            "*******secret*****",
            URLEncoder.encode( "*****secret*********", "UTF-8" )));

    DefaultHttpClient httpClient = new SystemDefaultHttpClient();
    HttpResponse response = httpClient.execute( request );

    assertThat( response, is( notNullValue() ));
    assertThat( response.getStatusLine().getStatusCode(), is( 200 ));
    assertThat( response.getEntity().getContentType().toString(), containsString( "text/html" ));

    String body = IOUtils.toString( response.getEntity().getContent() );

    assertThat( body, allOf(
            is( notNullValue()),
            containsString( "value=\"gQwCAShitcuP-_2OY58lgw3YW0AfbLE8m62mrvXWvQbiDLJk9QnDTs7pc0HH\"" )
    ));
}

This works fine when I run my unit tests from my IDE (IntelliJ), but when I run them under Maven's Surefire, this particular test fails: the server thread throws an exception as follows:

org.apache.jasper.JasperException: The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application

This of course causes the client side of the test to fail, since it gets back an error response instead of the expected login page.

So what's different between the two testing environments, and how can I make this test work in both?

回答1:

It turns out that by default Maven's Surefire plugin employs a trick wherein it creates a Manifest-Only JAR file, in order to avoid the need to pass the full classpath to the forked JRE (see this article). The Jetty JSP processor simply asks each classloader for its list of URLs, and so only sees the one manifest-only JAR file, rather than all the JAR files that are actually being used, and hence doesn't find the TLD files.

The simple solution, which is also documented on that page, is to turn off the useManifestOnlyJar option, as follows:

<plugins>
  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.18.1</version>
    <configuration>
      <useManifestOnlyJar>false</useManifestOnlyJar>
    </configuration>
  </plugin>
  ....
<plugin>

This instructs Surefire to directly pass the full classpath to the forked JRE process, thereby allowing it to locate the TLDs.