【问题标题】:How do you write a java unit test of a class method that does not return a value?如何编写不返回值的类方法的 java 单元测试?
【发布时间】:2022-01-19 13:34:57
【问题描述】:

我是 Java 单元测试的新手,需要帮助编写一个单元测试,该单元测试只需要处理方法没有返回值的“if”语句的 2 条路径。

public class Consumer extends KafkaConsumer {
    private final MessageService service;

    public Consumer(MessageService service) {
        this.service = service;
    }

    @Override
    protected void processRecords(ConsumerRecords<Long, String> records) {
        for (ConsumerRecord<Long, String> records) {
            if (record.value().startsWith("{")) {
                OldFormatMessageObject mo = new OldFormatMessageObject(record.value());
                service.processMessage(mo);
            } else if (record.value().startsWith("metadata")) {
                NewFormatMessageObject mo = new NewFormatMessageObject(record.value());
                service.processMessage(mo);
            }
        }
    }

【问题讨论】:

  • 这里需要用到mocks,然后验证unit和mocks的交互。好吧,例如,尝试使用 Mockito。
  • OldFormatMessageObjectNewFormatMessageObject 有一个共同的超类,对吧?它是什么?
  • 它们都是独立的类。

标签: java unit-testing


【解决方案1】:

要测试什么?

严格来说,分支逻辑实际上有3条路径可以测试:

  1. 如果startsWith("{") 则调用MessageService#processMessage(OldFormatMessageObject)
  2. 如果startsWith("metadata") 则调用MessageService#processMessage(NewFormatMessageObject)
  3. 否则什么都不会发生。

如果“迭代”逻辑也经过单元测试(for 循环),那就太好了。

如何测试?

我们必须验证,MessageService 的正确方法被调用了。

为此,我们可以使用MessageService mock,它将通过构造函数注入到Consumer

使用 mockito [1],它可能看起来像这样:

...
private static final String TEST_TOPIC = "test.topic";

@Mock
private MessageService messageServiceMock;

// Tested instance
private Consumer consumer;

@BeforeEach
public void beforeEach() {
    consumer = new Consumer(messageServiceMock);
}

@Test
public void newMessageFormatIsHandled() {
    // Given
    final String expectedValue = "my-expected-value";

    // When
    consumer.processRecords(testSingleConsumerRecords(expectedValue));

    // Then
    verify(messageServiceMock, times(1)).processMessage(new NewFormatMessageObject(expectedValue));
    // OR
    verify(messageServiceMock, times(1)).processMessage(isA(NewFormatMessageObject.class));
}

...

private static ConsumerRecords<Long, String> testSingleConsumerRecords(String expectedValue) {
    final Map<TopicPartition, List<ConsumerRecord<Long, String>>> records = new LinkedHashMap<>();
    final ConsumerRecord<Long, String> record = new ConsumerRecord<>(TEST_TOPIC, 1, 0, 100L, expectedValue);
    records.put(new TopicPartition(TEST_TOPIC, 0), Collections.singletonList(record));

    return new ConsumerRecords<>(records);
}


请注意,如果MessageService 有接口,您甚至不需要任何模拟框架,并为您的单元测试用例提供“模拟”实例,但模拟框架使测试更具可读性。


[1]https://site.mockito.org

【讨论】:

  • 我不确定如何处理您的“testSingleConsumerRecords(expectedValue) 方法。
  • 只需创建测试ConsumerRecords 实例 - 我已经更新了答案(请随意修改)。
  • 我在我的 ProcessRecords 方法中为“服务”变量获得了 NullExceptionPointer。
  • 这意味着,你的模拟没有初始化。请查阅您正在使用的模拟框架的文档 - 如果您将 mockito 与 JUnit 5 一起使用,请不要忘记使用 @ExtendWith(MockitoExtension.class) 注释您的测试类。
猜你喜欢
  • 2011-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-11
  • 2019-12-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多