运行 Spring MVC 测试时出现 NoSuchMethod 错误

NoSuchMethod error when running Spring MVC test

我正在尝试按照 Spring in Action(第 4 版)第 5 章中的示例来创建我自己的项目。 (仍然是企业级产品的新手)我正在使用 Windows 7 PC、Java 7、Spring 4 和 Maven。当我 运行 我的 ClinicalNoteControllerTest 时,测试失败并在特定行上出现 NoSuchMethod 错误。但是我已经研究过这一行,它似乎写对了。它绝对遵循书中的示例。我已经调试过了,但在出现的内容中找不到任何东西。我想知道我的 pom.xml 文件中是否没有正确的配置?

你们总是那么乐于助人,如果您能在这里找到解决方案,我将不胜感激。但我也想自己成为一个更好的故障排除者,而不仅仅是 运行 Whosebug 每次遇到问题时。因此,如果您能提供有关我如何自行解决此问题的任何提示,我们将不胜感激。

这是测试:

package com.kwalker.practicewellness;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.junit.Test;

import static org.mockito.Mockito.*;

import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
import static org.hamcrest.Matchers.*;

import org.springframework.web.servlet.view.InternalResourceView;

import com.kwalker.practicewellness.data.ClinicalNoteRepository;
import com.kwalker.practicewellness.domain.ClinicalNote;
import com.kwalker.practicewellness.web.ClinicalNoteController;

public class ClinicalNoteControllerTest {

@Test
public void shouldShowRecentClinicalNotes() throws Exception {
    List<ClinicalNote> expectedClinicalNotes = createClinicalNoteList(20);
    ClinicalNoteRepository mockNoteRepository = mock(ClinicalNoteRepository.class);
    when(mockNoteRepository.findClinicalNotes(Long.MAX_VALUE, 20)).thenReturn(expectedClinicalNotes);

    ClinicalNoteController noteController = new ClinicalNoteController(mockNoteRepository);
    MockMvc mockMvc = standaloneSetup(noteController).setSingleView(
            new InternalResourceView("/WEB-INF/views/clinicalNotes.jsp")).build();

    mockMvc.perform(get("/clinical-notes"))
        .andExpect(view().name("clinicalNotes"))
        .andExpect(model().attributeExists("clinicalNoteList"))
        .andExpect(model().attribute("clinicalNoteList", hasItems(expectedClinicalNotes.toArray())));
}

private List<ClinicalNote> createClinicalNoteList(int count) {
    List<ClinicalNote> clinicalNotes = new ArrayList<ClinicalNote>();
    for (int i=0; i < count; i++) {
        clinicalNotes.add(new ClinicalNote("Note " + i, new Date()));
    }
    return clinicalNotes;
}

}

这是 ClinicalNoteController:

package com.kwalker.practicewellness.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.kwalker.practicewellness.data.ClinicalNoteRepository;

@Controller
@RequestMapping("/clinical-notes")
public class ClinicalNoteController {

private ClinicalNoteRepository noteRepository;

@Autowired
public ClinicalNoteController(ClinicalNoteRepository noteRepository) {
    this.noteRepository = noteRepository;
}

@RequestMapping(method=RequestMethod.GET)
public String clinicalNotes(Model model) {
    model.addAttribute("clinicalNoteList", noteRepository.findClinicalNotes(Long.MAX_VALUE, 20));
    return "clinicalNotes";
}

}

这是我 运行 测试时在控制台上打印的内容:

INFO : org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder$StaticRequestMappingHandlerMapping - Mapped "{[/clinical-notes],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String com.kwalker.practicewellness.web.ClinicalNoteController.clinicalNotes(org.springframework.ui.Model)
INFO : org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter - Looking for @ControllerAdvice: org.springframework.test.web.servlet.setup.StubWebApplicationContext@41494678
INFO : org.springframework.mock.web.MockServletContext - Initializing Spring FrameworkServlet ''
INFO : org.springframework.test.web.servlet.TestDispatcherServlet - FrameworkServlet '': initialization started
INFO : org.springframework.test.web.servlet.TestDispatcherServlet - FrameworkServlet '': initialization completed in 16 ms

这是故障跟踪:

java.lang.NoSuchMethodError: org.hamcrest.Matcher.describeMismatch(Ljava/lang/Object;Lorg/hamcrest/Description;)V
at org.hamcrest.core.IsCollectionContaining.matchesSafely(IsCollectionContaining.java:31)
at org.hamcrest.core.IsCollectionContaining.matchesSafely(IsCollectionContaining.java:14)
at org.hamcrest.TypeSafeDiagnosingMatcher.matches(TypeSafeDiagnosingMatcher.java:55)
at org.hamcrest.core.AllOf.matches(AllOf.java:24)
at org.springframework.test.util.MatcherAssertionErrors.assertThat(MatcherAssertionErrors.java:65)
at org.springframework.test.web.servlet.result.ModelResultMatchers.match(ModelResultMatchers.java:56)
at org.springframework.test.web.servlet.MockMvc.andExpect(MockMvc.java:152)
at com.kwalker.practicewellness.ClinicalNoteControllerTest.shouldShowRecentClinicalNotes(ClinicalNoteControllerTest.java:39)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access[=15=]0(ParentRunner.java:42)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

这是我的 POM 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.kwalker</groupId>
<artifactId>practicewellness</artifactId>
<name>Practice Wellness</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>

<properties>
    <java-version>1.7</java-version>
    <org.springframework-version>4.1.4.RELEASE</org.springframework-version>
    <org.aspectj-version>1.6.10</org.aspectj-version>
    <org.slf4j-version>1.6.6</org.slf4j-version>

</properties>

<dependencies>
    <!-- Spring -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${org.springframework-version}</version>
        <exclusions>
            <!-- Exclude Commons Logging in favor of SLF4j -->
            <exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
             </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${org.springframework-version}</version>
    </dependency>

    <!-- AspectJ -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>${org.aspectj-version}</version>
    </dependency>   

    <!-- Logging -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${org.slf4j-version}</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>${org.slf4j-version}</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${org.slf4j-version}</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.15</version>
        <exclusions>
            <exclusion>
                <groupId>javax.mail</groupId>
                <artifactId>mail</artifactId>
            </exclusion>
            <exclusion>
                <groupId>javax.jms</groupId>
                <artifactId>jms</artifactId>
            </exclusion>
            <exclusion>
                <groupId>com.sun.jdmk</groupId>
                <artifactId>jmxtools</artifactId>
            </exclusion>
            <exclusion>
                <groupId>com.sun.jmx</groupId>
                <artifactId>jmxri</artifactId>
            </exclusion>
        </exclusions>
        <scope>runtime</scope>
    </dependency>

    <!-- @Inject -->
    <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
    </dependency>

    <!-- Servlet -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.2</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>

    <!-- Test -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.7</version>
        <scope>test</scope>
    </dependency> 
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>4.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-all</artifactId>
        <version>1.10.19</version>
    </dependency>
    <dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest-library</artifactId>
        <version>1.3</version>
    </dependency>

    <!-- Misc -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.3.2</version>
    </dependency>

</dependencies>
<build>
    <plugins>
        <plugin>
            <artifactId>maven-eclipse-plugin</artifactId>
            <version>2.9</version>
            <configuration>
                <additionalProjectnatures>
                    <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                </additionalProjectnatures>
                <additionalBuildcommands>
                    <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                </additionalBuildcommands>
                <downloadSources>true</downloadSources>
                <downloadJavadocs>true</downloadJavadocs>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.2</version>
            <configuration>
                <source>1.7</source>
                <target>1.7</target>
                <compilerArgument>-Xlint:all</compilerArgument>
                <showWarnings>true</showWarnings>
                <showDeprecation>true</showDeprecation>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>1.3.1</version>
            <configuration>
                <mainClass>org.test.int1.Main</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>

这是我的 webapp 初始化器:

package com.kwalker.practicewellness.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WellnessWebAppInitializer extends
    AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[] { RootConfig.class };
}

@Override
protected Class<?>[] getServletConfigClasses() {
    return new Class<?>[] { WebConfig.class };
}

@Override
protected String[] getServletMappings() {
    return new String[] { "/" };
}

}

这是我的 WebConfig 文件:

package com.kwalker.practicewellness.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan("com.kwalker.practicewellness.web")
public class WebConfig extends WebMvcConfigurerAdapter {

@Bean
public ViewResolver viewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setSuffix(".jsp");
    resolver.setExposeContextBeansAsAttributes(true);
    return resolver;
}

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    configurer.enable();
}

}

这是 RootConfig class:

package com.kwalker.practicewellness.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(basePackages={"com.kwalker.practicewellness"}, 
            excludeFilters={@Filter(type=FilterType.ANNOTATION, value=EnableWebMvc.class)})
public class RootConfig {

}

Ted Vinke 2013 年 12 月的博客解决了这个确切的问题:Mixing JUnit, Hamcrest and Mockito

我使用此页面上给出的代码修改了我的 POM 文件(主要涉及重新排列 mockito、junit 和 hamcrest,并排除了 mockito 依赖项上的 hamcrest)。重要提示:我使用的是这些资源的最新版本,直到我使 POM 文件中的版本与 link 页面上显示的版本匹配后,修复才起作用。这是来自 link:

的文本

This could be due to the fact that JUnit itself brings along its own version of Hamcrest as a transitive dependency. Now if you would be using JUnit 4.11 it would depending on Hamcrest 1.3 already.... Getting above error would be weird – since the describeMismatch method is present in org.hamcrest.Matcher interface. There seems to be an older version of org.hamcrest.Matcher present on the classpath to which org.junit.Assert.assertThat delegates. If you run this from Eclipse or IntelliJ, there’s a high chance that the IDE uses its own version of JUnit instead of your Maven dependency....

文章继续:

If we we’re looking in Eclipse for the Matcher classes we e.g. could see that there’s also one in mockito-all-1.9.5.jar.... Seems mockito-all is incompatible with JUnit 4.11 for backwards-compatibility reasons. The Hamcrest version 1.1 Matcher has been packaged within the dependency, so we can not exclude it.... Luckily for us, Mockito allows to us to use a mockito-core dependency instead of mockito-all. Running a dependency check (with dependency:tree or online) shows us it depends on hamcrest-core..... We can exclude it in the pom.xml and – no more NoSuchMethodError. Here’s the final combination of dependencies in our case:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>1.9.5</version>
    <exclusions>
        <exclusion>
            <artifactId>hamcrest-core</artifactId>
            <groupId>org.hamcrest</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <exclusions>
        <exclusion>
            <artifactId>hamcrest-core</artifactId>
            <groupId>org.hamcrest</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
</dependency>

我刚刚遇到了类似的问题,我试图跟踪 junit、mockito 和 hamcrest 作为依赖项导入的位置,以了解发生了什么变化(我们没有以任何方式更改代码)。

我们发现一个 "RELEASE" 值作为 pom 依赖项的版本(spring-来自 org.springframework 的测试)。所以看起来这个依赖项的新版本与其他库不兼容。删除版本标签后,问题就解决了。之后我们甚至删除了依赖项,因为它已经包含在其他依赖项中。