junit 使用Mockito模拟接口时出现空指针异常

ncecgwcz  于 2023-05-29  发布在  其他
关注(0)|答案(1)|浏览(300)

bounty将在19小时后到期。回答此问题可获得+100声望奖励。Swaraj Shekhar希望引起更多关注这个问题。

我得到空指针异常,而我试图模拟

@WebMvcTest(IMnJobManager.class)
public class CMnJobManagerTest {

    @Autowired
    private MockMvc mockmvc;

    @Test
    public void testExample()throws Exception{
        IMnAllWorkFlows allWorkFlows = Mockito.mock(IMnAllWorkFlows.class);

        Mockito.doAnswer(
                invocation -> {
                    return Arrays.asList( "modn-ops");
                }).when(allWorkFlows).getAllTenants();

        mockmvc.perform(get("/v1/tenant"))
                .andExpect(status().isOk())
                .andExpect(content().string("modn-ops"))
                .andDo(print());
    }
}

我收到以下错误:

java.lang.NullPointerException
        at com.test.manager.CMnJobManagerTest.testExample(CMnJobManagerTest.java:32)
        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.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
        at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
        at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
        at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
        at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
        at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
        at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
        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.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
        at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
        at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)

我的接口实现类如下所示:

@RestController
public class CMnJobManager implements IMnJobManager {
@Autowired
    public CMnJobManager(IMnAllWorkFlows allWorkFlows, IMnWorkflowService workflowService,
        IMnTemporalServiceClient temporalServiceClient, IMnWorkflowHistoryService workflowHistoryService,
        IMnSearchAttributeService searchAttributeService, IMnS3Tenant s3Tenant) {
    this.allWorkFlows = allWorkFlows;
    this.workflowService = workflowService;
    this.searchAttributeService = searchAttributeService;
    this.workflowHistoryService = workflowHistoryService;
    this.temporalServiceClient = temporalServiceClient;
    this.s3Tenant = s3Tenant;
    }
.
.
.

@Autowired
    private HttpServletRequest request;

    @Autowired
    private CMnCustomMetricService customMetricService;

}

接口有很多方法,但我只想模拟一个。其余的调用,将调用IMnJobManager接口,因此我模仿了它。
错误说明问题出在mockMvc.perform(...),因此出现了NullPointerException。
当我从MockMvc中删除Autowired并添加mockMvc = MockMvcBuilders.standaloneSetup(allWorkFlows).build();时,它给出404。
你知道这里有什么问题吗?

dm7nw8vv

dm7nw8vv1#

注解的作用以及如何正确使用Mockito来设置mock对象/test double似乎有很多困惑。
要开始,我建议阅读以下文章:

让我们先看看一些错误,最后再回来写一个工作测试。

@WebMvcTest(IMnJobManager.class)

@WebMvcTest annotation的value属性指定要在Spring上下文中加载的 ControllersIMnJobManager不是一个控制器,而是您的作业管理器界面。您希望传递CMnJobManager.class
@WebMvcTestonly 加载应用程序的Web上下文(例如控制器)。从注解的JavaDoc:
使用此注解将禁用完全自动配置,而仅应用与MVC测试相关的配置(即@Controller@ControllerAdvice@JsonComponentConverter/GenericConverterFilterWebMvcConfigurerHandlerMethodArgumentResolver beans,但不包括@Component@Service@Repository beans)。
因此您的服务bean都不会被加载。
在相同的JavaDoc中进一步向下:
通常,@WebMvcTest@MockBean@Import结合使用,以创建@Controller bean所需的任何协作者。
那是什么意思?

  1. @WebMvcTest(IMnJobManager.class)不会创建任何端点,因为IMnJobManager不是控制器,也没有定义任何请求Map。这一行显式地告诉Spring * 不加载 * 您的真实的控制器。尝试请求任何端点(使用mockmvc.perform(get(…)))将导致404/NOT_FOUND。
    1.没有加载任何服务,我们必须(手动)为您的控制器创建协作者。我们将在下一节中处理这个问题。
@Test
public void testExample()throws Exception{
    IMnAllWorkFlows allWorkFlows = Mockito.mock(IMnAllWorkFlows.class);

    doAnswer(invocation -> Arrays.asList("modn-ops"))
            .when(allWorkFlows)
            .getAllTenants();

    mockmvc.perform(get("/v1/tenant"))
            ...
}

这段代码创建了一个新的Mockito模拟对象,在其上存根一个方法,然后...什么都不做。示例是方法的本地示例,不存在于方法外部,也不能从外部访问。它 * 没有 * 在应用程序上下文中注册,并且不能由您的控制器使用。(现在是第二次阅读链接的Stackoverflow帖子的好时机)。
如何使示例作为控制器的协作者可用?我们按照注解的JavaDoc告诉我们的做:将其定义为@MockBean

@MockBean
private IMnAllWorkFlows allWorkFlows;

然后在setup/@BeforeEach方法中存根该方法。在简单的测试中,例如问题中,也可以在测试本身中配置存根。

@BeforeEach
void setUp() {
    when(allWorkFlows.getAllTenants()).thenReturn("modn-ops");
    // or: doAnswer(a -> Arrays.asList("modn-ops")).when(allWorkFlows).getAllTenants();
}

解决上述所有问题应:
1.加载Web上下文
1.示例化控制器
1.创建协作者
1.为方法设置存根
这是一个简单但完整的演示项目(即一个Minimal, Reproducible Example,包含编译和执行代码所需的所有类),这表明上面概述的解决方案确实有效:

DemoService.java

public interface DemoService {
    String get();
}

@Service
class DemoServiceImpl implements DemoService {
    @Override
    public String get() { return Instant.now().toString(); }
}

DemoController.java

@RestController
@RequiredArgsConstructor
public class DemoController {
    private final DemoService demoService;

    @GetMapping("demo")
    public String demo() { return demoService.get(); }
}

WebApplicationTest.java

@WebMvcTest(DemoController.class) // only loads a single controller
class WebApplicationTest {
    @Autowired private MockMvc mockMvc;
    @MockBean  private DemoService demoService; // add mock bean to context

    @BeforeEach
    void setUp() {
        // stub method:
        when(demoService.get()).thenReturn("stubbed value");
    }

    @Test
    void test() throws Exception {
        // perform GET request:
        mockMvc.perform(get("/demo"))
                .andExpect(status().isOk())
                .andExpect(content().string("stubbed value"))
                .andDo(print());
    }
}

相关问题