首页 > 解决方案 > 用于 TypeReference 的 Mockito 匹配器

问题描述

我正在尝试使用 Mockito 编写 Java 单元测试,但无法让匹配器工作。

我想测试以下课程

定制服务.java

public class CustomService {

private final ApplicationProperties props;

public CustomService(ApplicationProperties props){
    this.props = props;
}

private final ObjectMapper mapper = new ObjectMapper();
public void method(JsonNode message) throws CustomException {
    try {
        List<String> actions = mapper.readValue(message.get("actions").toString(), mapper.getTypeFactory().constructCollectionType(List.class, String.class));
        System.out.println(actions);
    } catch (IOException ex){
        throw new CustomException(ex);
    }
}
}

我有一个 CustomException 类

自定义异常.java

@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public class CustomException extends Exception {

public CustomException(Throwable cause) {
    super(cause);
    }

}

我想测试是否抛出了 CustomException。我正在使用 Mockito。我已经尝试过以下方法,但给定的语句似乎没有在方法中获取 (readValue) 行代码

CustomServiceTest.java

public class CustomServiceTest {

private final ApplicationProperties props = mock(ApplicationProperties.class);
private final CustomService customService = new CustomService(props);
private static final ObjectMapper objectMapper = new ObjectMapper();

@Test
public void CustomExceptionIsThrown() throws Exception {
    ObjectMapper mapper = mock(ObjectMapper.class);
    given(mapper.readValue(anyString(), any(TypeReference.class))).willThrow(new IOException("This is a test"));
    String json = "{\"actions\":[\"ac1\",\"ac2\",\"ac3\",\"ac4\"]}";
    JsonNode d = objectMapper.readTree(json);
    assertThrows(CustomException.class, () ->
            customService.method(d));
    }
}

运行测试时出现以下错误

Expected exception.CustomException to be thrown, but nothing was thrown..

帮助将不胜感激。

标签: javajunitmockito

解决方案


您的示例代码仍然存在一些问题。

  • 您没有将 Mock ofObjectMapper注入您的CustomService班级。

  • 由于您的测试类现在有一个 args 构造函数,您将无法将模拟注入您的测试。您将需要调整构造函数以ObjectMapper从外部传递或依靠Reflections手动放置模拟。

一般的建议是将ObjectMapperas 参数传递给构造函数。这样做,您将不需要更改finalon the field mapper

private final ApplicationProperties props;
private final ObjectMapper mapper;

public CustomService(ApplicationProperties props, ObjectMapper mapper){
    this.props = props;
    this.mapper = mapper;
}
  • getTypeFactory()调用该方法时,您还没有定义模拟的行为。此时您应该得到一个NullPointerException(如果您的代码实际使用了该模拟)。
    在下面的示例中,我使用RETURNS_DEEP_STUBS选项替换了这个定义,这导致mapper.getTypeFactory().constructCollectionType(List.class, String.class)返回一个CollectionType.

  • 模拟定义TypeReference不是必需的,因为它与调用方法时使用的方法参数不匹配readValue。相反,您将不得不使用CollectionType.

  • 由于您的测试方法是void您将不得不使用willThrow(...).given(...)语法。


这是一个工作测试的示例:(
仅适用于CustomService具有无参数构造函数的类)

@ExtendWith(MockitoExtension.class)
public class JacksonTest {

    static class CustomException extends Exception {
        public CustomException(IOException ex) {
            super(ex);
        }
    }

    static class CustomService { 
        private ObjectMapper mapper = new ObjectMapper();

        public void method(String c) throws CustomException {       
            try {
                mapper.readValue(c, mapper.getTypeFactory().constructCollectionType(List.class, String.class));            
            } catch (IOException ex){
                throw new CustomException(ex);
            }
        }
    }

    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    ObjectMapper mapper;

    @InjectMocks
    CustomService customService;

    @Test
    public void testCustomExceptionIsThrown() throws Exception {

        BDDMockito.willThrow(new IOException("This is a test")).given(mapper).readValue(ArgumentMatchers.anyString(), ArgumentMatchers.any(CollectionType.class));
        Assertions.assertThrows(CustomException.class, () -> customService.method("x")); 
    }
}

依赖项:

testCompile "org.junit.jupiter:junit-jupiter-api:5.5.2"
testCompile "org.junit.jupiter:junit-jupiter-engine:5.5.2"
testCompile "org.mockito:mockito-core:3.0.0"
testCompile "org.mockito:mockito-junit-jupiter:3.0.0"
compile "com.fasterxml.jackson.core:jackson-databind:2.9.9"

推荐阅读