【问题标题】:How to unit test a controller method with spring boot and mockito如何使用 spring boot 和 mockito 对控制器方法进行单元测试
【发布时间】:2021-06-25 15:38:14
【问题描述】:

我已经阅读了几篇文章并观看了一些视频,虽然我已经掌握了 Mockito 的工作原理,但我仍然无法对我的控制器进行任何测试。

首先,我有以下服务类,它执行对外部 API 的请求:

@Service
public class MovieService {

    RestTemplate restTemplate = new RestTemplate();
    
    public MovieResponse getMovieResponse() {
        ResponseEntity<MovieResponse> responseEntity = restTemplate.
                getForEntity("{base_url}", MovieResponse.class);
        return responseEntity.getBody();
    }

}

然后我的控制器调用这个方法,对数据进行排序操作:

@RestController
public class MovieController {
    
    @Autowired
    MovieService movieService = new MovieService();

    @GetMapping(value="/movies")
    public List<Movie> getAllMovies() {
        List<Movie> movies = movieService.getMovieResponse().getMovies();
        movies.sort(Comparator.comparing(Movie::getMovieName));
        return movies ;
    
    }
}

所以我尝试模拟服务响应以测试我的方法并验证是否正在发生排序。

@RunWith(MockitoJUnitRunner.class)
class MovieControllerTest{
    
    private MovieService movieService;
    
    @Autowired
    private MockMvc mockMvc;
    
    @Before
    void setup() {
        movieService = Mockito.mock(MovieService.class);
    }
    
    
    @Test
    void getAllMoviesTest() throws Exception {
        Movie movie1 = new Movie("Marley & Me", "2008");
        Movie movie2 = new Movie("Avatar", "2012");

        List<Movie> allMovies = Arrays.asList(movie2, movie1);
        List<Movie> expectedMovies = Arrays.asList(movie1, movie2);
        
        MovieResponse firstMovieResponse = new MovieResponse();
        firstMovieResponse.setMovies(allMovies);
        
        MovieResponse expectedMovieResponse = new MovieResponse();
        firstMovieResponse.setMovies(expectedMovies);
        
        Mockito.when(movieService.getMovieResponse())
                .thenReturn(firstMovieResponse);
        
        mockMvc.perform(get("/movies"))
                .andExpect(status().isOk())
                .andExpect((ResultMatcher) expectedMovieResponse);
  }
}

我尝试了几种方法,但我认为这是我所拥有的最接近的方法,但我在该行中得到了 NullPointerException

Mockito.when(movieService.getMovieResponse())
                    .thenReturn(firstMovieResponse);

堆栈跟踪打印以下内容:

java.lang.NullPointerException
    at com.example.movieservice.controller.MovieControllerTest.getAllMoviesTest(MovieControllerTest.java:50)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:84)
    at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:98)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:768)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)

【问题讨论】:

  • 查看堆栈跟踪。你把 NPE 放在哪条线上?
  • @SeanPatrickFloyd 它指出这条线是 NPE 的原因:Mockito.when(movieService.getMovieResponse()) .thenReturn(firstMovieResponse);

标签: spring-boot junit mockito


【解决方案1】:

您需要在由@Before 注释的setup 函数内的getAllMoviesTest 中进行设置。您还需要在测试类中使用@MockBean 注释movieService,但正如之前有人所说,查看堆栈跟踪以找出发生NullPointerException 的位置。

【讨论】:

  • 我已经检查了堆栈跟踪并将其添加到问题中,但我仍然无法理解为什么它会引发 NPE
【解决方案2】:

如果您的目标是控制排名,这可能会派上用场。

@RunWith(MockitoJUnitRunner.class)
class MovieControllerTest{
    
    @InjectMocks
    private MovieController movieController;

    @Mock
    private MovieService movieService;
    
    @Test
    void getAllMoviesTest() throws Exception {
        //Given
        Movie movie1 = new Movie("Marley & Me", "2008");
        Movie movie2 = new Movie("Avatar", "2012");
        final List<Movie> allMovies = Arrays.asList(movie2, movie1);
        
        MovieResponse firstMovieResponse = new MovieResponse();
        firstMovieResponse.setMovies(allMovies);
        
        Mockito.when(movieService.getMovieResponse()).thenReturn(firstMovieResponse);
        
        //When
        List<Movie> actualMovieList = movieController.getAllMovies();

        //Then
        assertThat(actualMovieList).containsExactly(movie1, movie2);
  }
}

@InjectMocks,将模拟类注入测试类。

PS。您还可以使用不同的东西来测试排序。

assertThat(actualMovieList).containsExactly(movie1, movie2);

【讨论】:

  • 抱歉回复晚了,但使用您的示例,我从 API 获得了实际响应,其中包含大量电影,因此断言失败
【解决方案3】:

通常单元测试意味着直接调用主题类并存根其依赖项。提供的代码示例依赖于 @Autowired 注释,该注释需要存在 Spring 上下文才能工作。

测试用例尝试调用不可用的mockMvc,因此抛出NullPointerException

您必须使用@SpringBootTest 显式注释测试类,以便创建上下文。此外,必须包含@AutoConfigureMockMvc,以便MockMvc 可用于自动装配。

最后但并非最不重要的一点是,由于我们大量使用 Spring 的上下文,而不是使用普通的 Mockito,我们必须用 Spring 的 @MockBean 包装它,这会将存根注入控制器。

@SpringBootTest
@AutoConfigureMockMvc
class MovieControllerTest {

    @MockBean
    private MovieService movieService;

    @Autowired
    private MockMvc mockMvc;

    @Test
    void getAllMoviesTest() throws Exception {
        // TODO: when(movieService...).thenReturn(...);
        // TODO: mockMvc.perform(...);
    }
}

有关更多信息,请参阅Spring's documentation

【讨论】:

  • 我猜真正缺少的是测试开始时的 AutoConfigure 行
【解决方案4】:

我认为 NPE 即将到来,因为 movieservice 没有被嘲笑,并且 movieService 对象可能为 null,所以它可能会给出 NPE。尝试将 @MockBean 放在 moveservice 之上。 尝试在调试模式下运行测试。

【讨论】:

    猜你喜欢
    • 2019-07-07
    • 2014-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-28
    • 1970-01-01
    • 2018-10-17
    相关资源
    最近更新 更多