模拟一个 ObjectInputStream
Mocking an ObjectInputStream
当我尝试模拟 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?
您不能模拟 final
方法,因为它不可覆盖并且 readObject()
是 final
:
public final Object readObject(){...}
为了实现您的目标,您可以重构您的实际代码。
例如,您可以引入一个包装器 class 来保存一个 ObjectInputStream
实例并将处理委托给它。
通过这种方式,您可以模拟此包装器 class.
的 readObject()
方法
您也可以使用比 Mockito 提供更多功能的 Powermock,但我真诚地避免使用它。
这是由于readObject()是final造成的。
Mockito 无法模拟 final 方法。所以发生的事情是 指定 调用模拟对象的尝试……完全出错了。是的,抛出 NPE 的事实不仅仅是误导。
您可以尝试使用 Mockito 2 中允许模拟 final 方法的 experimental 功能。如果这对您不起作用,PowerMock(ito) 或 JMockit 很可能会完成这项工作。
郑重声明:这些微妙的问题是不再使用 Java 内置序列化的另一个好理由。
绕过这个的唯一其他方法:针对相应的 接口 ObjectInput,而不是特定的实现 class.
进行编程
因为您可以轻松地模拟 界面 。
当然,这意味着更改您的生产代码。这可能不是一个坏主意,因为它 将您的业务逻辑与 实际 序列化形式分离。如果您稍后选择序列化为 JSON 字符串,使用 GSON - 您只需替换该接口的实现即可。
正如其他人所述,readObject()
是 ObjectInputStream
中的 final
。
我感谢其他人确实建议 PowerMock 强制模拟这个 class!
更好的解决方案是遵循 针对接口编程 模式。方法 readObject()
在由 ObjectInputStream
实现的接口 ObjectInput
中声明,因此您可以更改 class 的签名以使用接口 ObjectInput
而不是具体的classObjectInputStream
。
模拟接口 ObjectInput
是小菜一碟...
could you add an MCVE of how this is meant to look like? Nobody did this so far. – 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");
}
}
my particular problem is with writeObject(). Specifically verify(serverInstance).ObjectOutPutStreamToClient.writeObject(someValue);
Do you have a nifty solution for this as well? I recon this is harder because writeObject
is 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");
}
}
当我尝试模拟 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?
您不能模拟 final
方法,因为它不可覆盖并且 readObject()
是 final
:
public final Object readObject(){...}
为了实现您的目标,您可以重构您的实际代码。
例如,您可以引入一个包装器 class 来保存一个 ObjectInputStream
实例并将处理委托给它。
通过这种方式,您可以模拟此包装器 class.
的 readObject()
方法
您也可以使用比 Mockito 提供更多功能的 Powermock,但我真诚地避免使用它。
这是由于readObject()是final造成的。
Mockito 无法模拟 final 方法。所以发生的事情是 指定 调用模拟对象的尝试……完全出错了。是的,抛出 NPE 的事实不仅仅是误导。
您可以尝试使用 Mockito 2 中允许模拟 final 方法的 experimental 功能。如果这对您不起作用,PowerMock(ito) 或 JMockit 很可能会完成这项工作。
郑重声明:这些微妙的问题是不再使用 Java 内置序列化的另一个好理由。
绕过这个的唯一其他方法:针对相应的 接口 ObjectInput,而不是特定的实现 class.
进行编程因为您可以轻松地模拟 界面 。
当然,这意味着更改您的生产代码。这可能不是一个坏主意,因为它 将您的业务逻辑与 实际 序列化形式分离。如果您稍后选择序列化为 JSON 字符串,使用 GSON - 您只需替换该接口的实现即可。
正如其他人所述,readObject()
是 ObjectInputStream
中的 final
。
我感谢其他人确实建议 PowerMock 强制模拟这个 class!
更好的解决方案是遵循 针对接口编程 模式。方法 readObject()
在由 ObjectInputStream
实现的接口 ObjectInput
中声明,因此您可以更改 class 的签名以使用接口 ObjectInput
而不是具体的classObjectInputStream
。
模拟接口 ObjectInput
是小菜一碟...
could you add an MCVE of how this is meant to look like? Nobody did this so far. – 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");
}
}
my particular problem is with writeObject(). Specifically
verify(serverInstance).ObjectOutPutStreamToClient.writeObject(someValue);
Do you have a nifty solution for this as well? I recon this is harder becausewriteObject
isvoid
. – 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");
}
}