模拟ObjectInputStream

问题描述 投票:2回答:3

当我尝试模拟ObjectInputStream对象时,我得到一个NullPointerException。更确切地说,当这条线被称为:when(inputStream.readObject()).thenReturn(new Person("Doe", "John", "123"));

代码片段:

@RunWith(MockitoJUnitRunner.class)
public class ConnectionTest{
  ...
  @Mock
  private ObjectInputStream inputStream;
  ...
  @Test
  public void test(){
   ...
    when(inputStream.readObject()).thenReturn(new Person("Doe", "John", "123"));
  ...
  }
}

通常,当你初始化ObjectInputStream时,你必须传递一个InputStream对象作为构造函数参数,我敢打赌这是问题 - 没有为ObjectInputStream分配InputStream。

我应该如何正确地模拟ObjectInputStream呢?

java unit-testing mockito objectinputstream
3个回答
2
投票

这是因为readObject()是最终的。

Mockito无法模拟最终方法。所以会发生的是,尝试指定对模拟对象的调用......简直就是错误的。是的,NPE被抛出的事实不仅仅是误导。

你可以尝试在Mockito 2中使用那个允许模拟最终方法的experimental功能。如果这对您不起作用,PowerMock(ito)或JMockit很可能会完成这项工作。

而且只是为了记录:这些微妙的问题是不再使用Java内置序列化的另一个好理由。

绕过这个的唯一方法是:针对相应接口ObjectInput的程序,而不是具体的实现类。

因为你可以轻松地模拟该界面。

但当然,这意味着要更改您的生产代码。这可能不是一个坏主意,因为它将您的业务逻辑与实际的序列化形式分离开来。如果您稍后选择使用GSON序列化为JSON字符串 - 您只需替换该接口的实现。


3
投票

正如其他人所说,readObject()finalObjectInputStream

我很欣赏其他人的建议 PowerMock 强迫这个类的模拟!

更好的解决方案是遵循该程序对接口模式。方法readObject()ObjectInput实现的接口ObjectInputStream中声明。因此,您可以更改类的签名以使用接口ObjectInput而不是具体类ObjectInputStream

嘲笑界面ObjectInput是小菜一碟......


你可以添加一个看起来像这样的MCVE吗?到目前为止,没有人这样做过。 - Tobias Kolb

这里是:

production code:

public class Person {
    public Person(String string, String string2, String string3) {
    }
}

class ClassUnderTest {
    private final ObjectInput objectInput;

    public ClassUnderTest(ObjectInput inputStream) {
        objectInput = inputStream;
    }

    public Person readFromObjectStreamAsSideEffect() {
        try {
            return (Person) objectInput.readObject();
        } catch (ClassNotFoundException | IOException e) {
            throw new RuntimeException("some meaningful explanation.", e);
        }
    }
}

Test code

@ExtendWith(MockitoExtension.class) // allows for other runner...
public class ConnectionTest {

    @Mock
    ObjectInput inputStream;

    // @InjectMocks
    // compiler will NOT complain if constructor arguments are missing, so I discourage this.

    ClassUnderTest cut; // do not initialize here, mock is still NULL.

    @BeforeEach
    private void setup() {
        cut = new ClassUnderTest(inputStream);
    }

    @Test
    public void getPreparedObjectFromInputStreamy() throws Exception {
        Person preparedValueObject = new Person("Doe", "John", "123");
        when(inputStream.readObject()).thenReturn(preparedValueObject);

        Person result = cut.readFromObjectStreamAsSideEffect();

        assertEquals(preparedValueObject, result, "hint for reason of failing");
    }   
}

我的特殊问题是writeObject()。特别是verify(serverInstance).ObjectOutPutStreamToClient.writeObject(someValue);你有一个漂亮的解决方案吗?我认为这更难,因为writeObjectvoid。 - Tobias Kolb

我不是我,但Mockito有一个解决方案:

Production code

public class Person {
    public Person(String string, String string2, String string3) {
    }
    // having toString() too improves fail message of test.
}

class ClassUnderTest {
    private final ObjectOutput objectOutput;

    public ClassUnderTest(ObjectOutput objectOutputStream) {
        objectOutput = objectOutputStream;
    }

    public void writeObjects(List<Person> persons) {
        try {
            for (Person person : persons) {
                objectOutput.writeObject(person);
            }
        } catch (IOException e) {
            throw new RuntimeException("some meaningfull explanation.", e);
        }
    }
}

test code

@ExtendWith(MockitoExtension.class)
public class ConnectionTest {

    @Mock
    ObjectOutput outputStream;

    ClassUnderTest cut;

    @BeforeEach
    private void setup() {
        cut = new ClassUnderTest(outputStream);
    }

    @Test
    public void getPreparedObjectFromInputStreamy() throws Exception {
        List<Person> listToWrite = Arrays.asList(//
                new Person("Doe", "John", "123"),
                new Person("Doe", "Jane", "456"));

        cut.writeObjects(listToWrite);

        ArgumentCaptor<Person> passedArgument = ArgumentCaptor.forClass(Person.class);
        verify(outputStream, times(listToWrite.size())).writeObject(passedArgument.capture());
        assertTrue(passedArgument.getAllValues().contains(listToWrite.get(0)), "hint for reason of failing");
        assertTrue(passedArgument.getAllValues().contains(listToWrite.get(1)), "hint for reason of failing");
    }   
}

1
投票

你不能模拟一个final方法因为它不可覆盖而且readObject()final

public final Object readObject(){...}

为了实现您的目标,您可以重构您的实际代码。 例如,您可以引入一个包装类,该类包含ObjectInputStream实例并将处理委托给它。 通过这种方式,您可以模拟此包装类的readObject()方法。 您也可以使用Powermock提供比Mockito更多的功能,但我真诚地避免使用它。

© www.soinside.com 2019 - 2024. All rights reserved.