【问题标题】:Mockito | JUnit | Avro: Mocking a void method does not work莫基托 | JUnit | Avro:模拟 void 方法不起作用
【发布时间】:2020-09-08 07:52:07
【问题描述】:

我写了一个avro序列化的方法,我想测试一下。

方法

@SneakyThrows
@Override
public byte[] serialize(String topic, T data) {
    try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
        writeSerializedAvro(stream, data, data.getSchema());
        return stream.toByteArray();
    } catch (IOException e) {
        throw new AvroSerializationException(e.getMessage(), e);
    }
}

private void writeSerializedAvro(ByteArrayOutputStream stream, T data, Schema schema) throws IOException {
    binaryEncoder = EncoderFactory.get().binaryEncoder(stream, binaryEncoder);
    DatumWriter<T> datumWriter = new GenericDatumWriter<>(schema);
    datumWriter.write(data, binaryEncoder);
    binaryEncoder.flush();
}

datumWriter.writebinaryEncoder.flush() 可以抛出IOException

在测试中,我想看看IOException是否被抛出,我的方法捕获它并将其包装在AvroSerializationException(我编写的一个类)中。

单元测试

    @Test(expected = AvroSerializationException.class)
public void testSerializeException() throws IOException {
    // Given 
// need to mock here

    // When
    serializer.serialize("", avroObject);
}

我尝试使用 doThrow().when() 方法模拟 GenericDatumWriterBufferedBinaryEncoderEncoderFactory,但对我没有任何效果。

BufferedBinaryEncoder binaryEncoder = Mockito.mock(BufferedBinaryEncoder.class);
Mockito.doThrow(new IOException()).when(binaryEncoder).flush();

    GenericDatumWriter datumWriter = Mockito.mock(GenericDatumWriter.class);
    Mockito.doThrow(new IOException()).when(datumWriter).write(any(), any());

但问题又回到了,我应该如何将这些对象传递给我的序列化程序,同时牢记最佳实践?

【问题讨论】:

  • 请参阅minimal reproducible example。 “我试过了”......但是你的代码在哪里做呢?!嘲笑的重点是:你需要知道你在做什么。你没有告诉我们你到底做了什么,那么你希望我们告诉你什么?
  • 除此之外,我觉得真正的答案是:你应该退后一步,真正研究如何使用这些模拟框架。您可能听说过“好吧,我可以模拟事物”,但似乎还没有人告诉您:模拟对象以某种方式需要传递给您的生产代码,以便实际调用它们。换句话说:单独的模拟框架是没有意义的。您必须学习如何编写可测试的代码,以便您可以轻松使用这样的框架,从而获得体面的生产和测试代码。
  • @GhostCat 同意你的观点。我已经习惯于在 Python 中进行单元测试时修补类,这是我在 Java 中无法做到的(或者我可以做到吗?)......那么你有什么建议?有一个 DatumWriter 的设置器对我来说看起来像是糟糕的代码。
  • @GhostCat 添加了我尝试过的内容。
  • 有多种方法可以到达那里。一种方法:有一个(非公共)构造函数,将该对象作为参数(除非该对象已设置为某个 ctor 参数)。或者,还有 InjectMocks 注释。这确实有一些魔力,但问题是当它注入失败时你不会得到任何错误。回到我的观点:这不是你可以根据 5 分钟的反馈 stackoverflow 获得的东西。如果你想用 java 和 mockito 做真正的工作,你必须硬着头皮坐上一两天,学习如何正确地做事。

标签: java serialization junit mockito avro


【解决方案1】:

您需要首先使用 Mockito 模拟对象并在您的类中使用它,然后将引发异常,如下所示:

您的代码:

private DatumWriter<T> datumWriter = new GenericDatumWriter<>(schema);

@SneakyThrows
@Override
public byte[] serialize(String topic, T data) {
    try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
        writeSerializedAvro(stream, data, data.getSchema());
        return stream.toByteArray();
    } catch (IOException e) {
        throw new AvroSerializationException(e.getMessage(), e);
    }
}

private void writeSerializedAvro(ByteArrayOutputStream stream, T data, Schema schema) throws IOException {
    binaryEncoder = EncoderFactory.get().binaryEncoder(stream, binaryEncoder);
    datumWriter.write(data, binaryEncoder);
    binaryEncoder.flush();
}

public void setDatumWriter (DatumWriter datumWriter){
   this.datumWriter=datumWriter;
}

您的测试代码:

@Test(expected = AvroSerializationException.class)
public void testSerializeException() throws IOException {
    // Given 
    DatumWriter<YourClass> datumWriter = Mockito.mock(DatumWriter<YourClass>.class);
    when(datumWriter.write(anyObject(), anyObject())).thenThrow(IOException.class);
    
    serializer.setDatumWriter(datumWriter);  
   
    serializer.serialize("", avroObject);
}

【讨论】:

  • 您应该强调这只是一个建议如何使事情可测试。制作一个 setter 只是为了测试是一个很大的代码味道。
  • 是的,我知道。不幸的是,我们不能在不注入模拟对象的情况下使用 Mockito。这是框架的坏处,框架帮助你实现高级功能,另一方面,你需要将你的代码与之耦合。
  • 不完全正确。 Mockito 有 InjectMocks 注释,它使用反射将对象“获取”到您的被测生产对象中。但是该注释也有一些方面使我无法使用它
  • 改用构造函数怎么样?
  • 当然,例如,我们通常会做“构造函数伸缩”。但是,这取决于人们对保持类的小规模有多好……这可能会导致令人讨厌的冗长构造函数,这些构造函数需要大量对象。但是,是的,我更喜欢注入注解。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多