0

I'm getting this error when submitting login form with unit test:

org.springframework.web.servlet.PageNotFound noHandlerFound
WARNING: No mapping found for HTTP request with URI [/app/login/authenticate] in DispatcherServlet with name ''

I'm using Spring 4.0.5.RELEASE with Spring Security 4.0.0.M1.

This is how I have configured my test class:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {
        UnitTestContext.class,
        SecurityContext.class,
        ServletContext.class
})
@WebAppConfiguration
public class LoginControllerTest {

    @Autowired
    private WebApplicationContext webAppContext;
    @Autowired
    private FilterChainProxy springSecurityFilterChain;

    private MockMvcHtmlUnitDriver webDriver;

    @Before
    public void setUp() {
        MockMvc mockMvc = MockMvcBuilders
                .webAppContextSetup(webAppContext)
                .addFilter(springSecurityFilterChain)
                .build();
        webDriver = new MockMvcHtmlUnitDriver(mockMvc);
    }

    @Test
    public void test() {
        LoginPage loginPage = LoginPage.get(webDriver).login(LoginPage.class);
    }

}

Login page class:

public class LoginPage extends AbstractPage {

    public static final String URL = "http://localhost:8080/app/login";

    @FindBy(id = "username")
    private WebElement usernameElement;
    @FindBy(id = "password")
    private WebElement passwordElement;
    @FindBy(id = "remember-me")
    private WebElement rememberMeElement;
    @FindBy(id = "login-btn")
    private WebElement loginBtnElement;
    @FindBy(id = "login-form")
    private WebElement loginForm;

    public LoginPage(WebDriver webDriver) {
        super(webDriver);
    }

    public LoginPage typeUsername(String username) {
        usernameElement.sendKeys(username);
        return this;
    }

    public String getUsername() {
        return usernameElement.getAttribute("value");
    }

    public LoginPage typePassword(String password) {
        passwordElement.sendKeys(password);
        return this;
    }

    public String getPassword() {
        return passwordElement.getAttribute("value");
    }

    public LoginPage selectRememberMe() {
        rememberMeElement.click();
        return this;
    }

    public boolean isRememberMe() {
        return rememberMeElement.isSelected();
    }

    public <T> T login(Class<T> resultPage) {
        loginBtnElement.click();
        return PageFactory.initElements(webDriver, resultPage);
    }

    public static LoginPage get(WebDriver webDriver) {
        webDriver.get(URL);
        return PageFactory.initElements(webDriver, LoginPage.class);
    }

}

And here's SecurityContext.class where I have declared the /login/authenticate mapping:

@Configuration
@EnableWebSecurity
public class SecurityContext extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserMapper userMapper;

    @Override
    public void configure(WebSecurity web) throws Exception {
        web
            .ignoring()
                .antMatchers("/static/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers();

        http
            .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/login/authenticate")
                .failureHandler(new SimpleUrlAuthenticationFailureHandler("/loginfailed"));
    }

    @Override
    public UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl(userMapper);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .userDetailsService(userDetailsService())
            .passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(10);
    }

}

-

@Configuration
public class UnitTestContext {

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();

        messageSource.setDefaultEncoding("UTF-8");
        messageSource.setBasenames(
                "i18n/messages",
                "i18n/validation-messages");
        messageSource.setFallbackToSystemLocale(false);

        return messageSource;
    }

    @Bean
    public UserService userService() {
        return Mockito.mock(UserService.class);
    }

    @Bean
    public UserMapper userMapper() {
        return Mockito.mock(UserMapper.class);
    }

}

-

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {
        "com.app.common.controller",
        "com.app.home.controller",
        "com.app.user.controller"
})
public class ServletContext extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("/static/")
                .setCachePeriod((int) TimeUnit.DAYS.toSeconds(365));
    }

    @Bean
    public ThymeleafViewResolver viewResolver() {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();

        viewResolver.setTemplateEngine(templateEngine());
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setContentType("text/html; charset=UTF-8");

        return viewResolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();

        templateEngine.setTemplateResolver(templateResolver());
        templateEngine.setAdditionalDialects(new HashSet<>(Arrays.asList(
                new SpringSecurityDialect())));

        return templateEngine;
    }

    @Bean
    public ServletContextTemplateResolver templateResolver() {
        ServletContextTemplateResolver templateResolver =
                new ServletContextTemplateResolver();

        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode("HTML5");
        templateResolver.setCharacterEncoding("UTF-8");

        return templateResolver;
    }

}

Login page:

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org" th:include="layouts/main-layout :: main-layout">
<head>
    <title th:text="#{login.page.title}"></title>
</head>
<body>
    <div class="col-sm-6 col-sm-offset-3" th:fragment="content">
        <form class="default-form" method="post" action="login/authenticate" role="form"
                th:object="${loginForm}">
            <input type="hidden"
                th:name="${_csrf.parameterName}"
                th:value="${_csrf.token}" />
            <div class="default-form-header">
                <h3 th:text="#{login.header.text}"></h3>
            </div>
            <fieldset>
                <div class="form-group">
                    <label for="username" th:text="#{login.username.label}"></label>
                    <div class="input-group">
                        <span class="input-group-addon">
                            <i class="fa fa-user fa-fw"></i>
                        </span>
                        <input type="text"
                            id="username"
                            class="form-control"
                            th:field="*{username}"
                            th:maxlength="${usernameMaxLength}"
                            th:placeholder="#{login.username.label}" />
                    </div>
                </div>
                <div class="form-group">
                    <label for="password" th:text="#{login.password.label}"></label>
                    <div class="input-group">
                        <span class="input-group-addon">
                            <i class="fa fa-key fa-fw"></i>
                        </span>
                        <input type="password"
                            id="password"
                            class="form-control"
                            th:field="*{password}"
                            th:maxlength="${passwordMaxLength}"
                            th:placeholder="#{login.password.label}" />
                    </div>
                </div>
                <div class="form-group">
                    <div class="checkbox">
                        <label>
                            <input type="checkbox"
                                id="remember-me" />
                            <span th:text="#{login.remember.checkbox}"></span>
                        </label>
                    </div>
                </div>
            </fieldset>
            <div class="default-form-footer">
                <button type="submit"
                    id="login-btn"
                    class="btn btn-primary"
                    th:text="#{login.submit.button}">
                </button>
            </div>
        </form>
    </div>
</body>
</html>

Everything works nice when it's running on Tomcat. Am I missing some filter or what?

While debugging I found out that urlMap in AbstractHandlerMethodMapping contains only these urls: (/login/authenticate is missing)

{
    /=[{[/],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}],
    /loginfailed=[{[/loginfailed],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}],
    /login=[{[/login],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}],
    /signup=[{[/signup],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]},
    {[/signup],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}]
}

This is what comes from print()

MockHttpServletRequest:
         HTTP Method = GET
         Request URI = /app/login
          Parameters = {}
             Headers = {Content-Type=[*/*;charset=ISO-8859-1], Accept=[image/gif, image/jpeg, image/pjpeg, image/pjpeg, */*], Accept-Encoding=[gzip, deflate], Accept-Language=[en-us]}

             Handler:
                Type = com.app.user.controller.LoginController
              Method = public java.lang.String com.app.user.controller.LoginController.showLoginPage(org.springframework.ui.ModelMap)

               Async:
   Was async started = false
        Async result = null

  Resolved Exception:
                Type = null

        ModelAndView:
           View name = login
                View = null
           Attribute = usernameMaxLength
               value = 30
           Attribute = passwordMaxLength
               value = 30
           Attribute = loginForm
               value = com.app.user.dto.LoginForm@fa8227
              errors = []

            FlashMap:

MockHttpServletResponse:
              Status = 200
       Error message = null
             Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY], Content-Type=[text/html; charset=UTF-8]}
        Content type = text/html; charset=UTF-8
       Forwarded URL = null
      Redirected URL = null
             Cookies = []
kesäkuuta 01, 2014 9:10:51 AP. org.springframework.web.servlet.PageNotFound noHandlerFound
WARNING: No mapping found for HTTP request with URI [/app/login/authenticate] in DispatcherServlet with name ''

MockHttpServletRequest:
         HTTP Method = POST
         Request URI = /app/login/authenticate
          Parameters = {_csrf=[12e09c07-32f7-4dee-8ad0-35bfa1827354], username=[], password=[]}
             Headers = {Content-Type=[*/*;charset=UTF-8], Accept=[*/*], Referer=[http://localhost:8080/app/login], Accept-Encoding=[gzip, deflate], Accept-Language=[en-us]}

             Handler:
                Type = null

               Async:
   Was async started = false
        Async result = null

  Resolved Exception:
                Type = null

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

            FlashMap:

MockHttpServletResponse:
              Status = 404
       Error message = null
             Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
        Content type = null
                Body = 
       Forwarded URL = null
      Redirected URL = null
             Cookies = []
4

1 回答 1

0

这是 HmtlUnitRequestBuilder 中的一个错误,它如何将 HtmlUnit 的请求转换为 Spring 的 MockHttpServletRequest。您可以在spring-test-mvc-htmlunit/issues/28中找到更多详细信息。

我已经向 master 推出了一个修复程序,因此您可以更新到spring-test-mvc-htmlunit 的最新快照来解决您的问题。

于 2014-06-02T15:09:25.267 回答