当我尝试模拟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呢?
这是因为readObject()是最终的。
Mockito无法模拟最终方法。所以会发生的是,尝试指定对模拟对象的调用......简直就是错误的。是的,NPE被抛出的事实不仅仅是误导。
你可以尝试在Mockito 2中使用那个允许模拟最终方法的experimental功能。如果这对您不起作用,PowerMock(ito)或JMockit很可能会完成这项工作。
而且只是为了记录:这些微妙的问题是不再使用Java内置序列化的另一个好理由。
绕过这个的唯一方法是:针对相应接口ObjectInput的程序,而不是具体的实现类。
因为你可以轻松地模拟该界面。
但当然,这意味着要更改您的生产代码。这可能不是一个坏主意,因为它将您的业务逻辑与实际的序列化形式分离开来。如果您稍后选择使用GSON序列化为JSON字符串 - 您只需替换该接口的实现。
正如其他人所说,readObject()
是final
的ObjectInputStream
。
我很欣赏其他人的建议 PowerMock 强迫这个类的模拟!
更好的解决方案是遵循该程序对接口模式。方法readObject()
在ObjectInput
实现的接口ObjectInputStream
中声明。因此,您可以更改类的签名以使用接口ObjectInput
而不是具体类ObjectInputStream
。
嘲笑界面ObjectInput
是小菜一碟......
你可以添加一个看起来像这样的MCVE吗?到目前为止,没有人这样做过。 - Tobias Kolb
这里是:
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);
}
}
}
@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);
你有一个漂亮的解决方案吗?我认为这更难,因为writeObject
是void
。 - Tobias Kolb
我不是我,但Mockito有一个解决方案:
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);
}
}
}
@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");
}
}
你不能模拟一个final
方法因为它不可覆盖而且readObject()
是final
:
public final Object readObject(){...}
为了实现您的目标,您可以重构您的实际代码。
例如,您可以引入一个包装类,该类包含ObjectInputStream
实例并将处理委托给它。
通过这种方式,您可以模拟此包装类的readObject()
方法。
您也可以使用Powermock提供比Mockito更多的功能,但我真诚地避免使用它。