如何模拟objectOutputStream.writeObject()?

时间:2019-03-24 16:59:05

标签: java mockito

我要做什么

我有一个服务器类,故意没有将任何参数传递给它,并且想用Mockito对其进行测试。

如果您想在Github上查看完整的源代码:

Server.class

public class Server extends Thread {
    private Other other;
    ObjectInputStream fromClient;
    ObjectOutputStream toClient;


    public Server(){
        this.other = new Other(foo, bar);
    }


    @Override
    public void run(){
        try{

            ServerSocket serverSocket = new ServerSocket(1337);
            Socket socket = serverSocket.accept();

            fromClient = new ObjectInputStream(socket.getInputStream());
            toClient = new ObjectOutputStream(socket.getOutputStream());

            while(true) {
                int command = (Integer) fromClient.readObject();
                switch (command) {
                    case 0x1:
                        //add
                        //...
                        break;
                    case 0x2:
                        //get server data
                        toClient.writeObject(other.getSomething());
                        break;
                    case 0x3:
                        //delete
                        //...
                        break;
                    default:
                        break;
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        Thread t = new Server();
        t.start();
    }
}

问题

我知道Mockito不能模拟最终类,例如ObjectOutputStream和ObjectInputStream。

这正是我遇到问题的地方。

到目前为止,我的测试失败,并且在线上出现NullPointerException

when(server.fromClient.readObject()).thenReturn(0x2);

当运行最终方法时,这对于Mockito是典型的。

ServerTest.class

@Test
    void run() throws IOException, ClassNotFoundException {
        //given
        Other other = new Other(foo, bar);
        Server server = mock(Server.class);

        //when
        when(server.fromClient.readObject()).thenReturn(0x2);

        server.start();

        //then
        verify(server).toClient.writeObject(other.getSomething());

    }

我尝试过的

other posts中建议通过实现接口ObjectInput来更改被测类的签名,从而可以绕过 final 问题,从而进行模拟无论如何。

但是,在提出的方法中,尚不清楚当不将ObjectOutputStream作为被测类的参数传递时如何操作。

此外,如通过我的ObjectInputStream结构所见,当ObjectOutputStream直接控制case的响应时该如何操作,这对于TCP客户端/服务器应用程序来说并非不常见

到目前为止,我一直认为我的测试会起作用,而不是ObjectOutputStream签名中的final关键字。如果我错了,请在这里纠正我。

Q:“为什么不将流传递到服务器?” 答:因为我在其他任何地方都不需要它们。

这确实是最后的努力,如果需要的话,我会的,但是我宁愿不这样做。

2 个答案:

答案 0 :(得分:2)

您可以在Server类中提供一个函数,该函数读取fromClient输入流并模拟它,而不是ObjectInputStream.readObject()方法:

public class Server extends Thread {
     private Other other;
     ObjectInputStream fromClient;
     ObjectOutputStream toClient;


     public Server(){
         this.other = new Other(foo, bar);
     }

     Object readObject() throws ClassNotFoundException, IOException {
         return fromClient.readObject();
     }

     //...

因此您的测试应如下所示:

    @Test
    void run() throws IOException, ClassNotFoundException {
        //given
        Other other = new Other(foo, bar);
        Server server = mock(Server.class);

        //when
        when(server.readObject()).thenReturn(0x2);

        server.start();

        //then
        verify(server).toClient.writeObject(other.getSomething());
    }

同一件事也可以应用于toClient流。

答案 1 :(得分:1)

对您的问题的误解

  

我知道Mockito不能模拟最终类,例如ObjectOutputStream和ObjectInputStream。 >   这正是我遇到问题的地方。

     

到目前为止,我的测试失败,并且在线上出现NullPointerException

     

当(server.fromClient.readObject())。thenReturn(0x2);。

     

当运行最终方法时,这对于Mockito是典型的。

您的NPE不是不是,是因为Mockitos无法模拟final方法。使用NPE的原因是您的成员变量fromClienttoClient在测试代码尝试访问它们时未初始化。

但是,即使这样,它们也将使用实际依赖项实例而不是 mocks 进行初始化,这样Mockito仍然无法对其进行配置。

您从根本上误解了mocks的目的和技术含义。 您从不模拟待测课程。 您总是要模拟被测类的依赖项,并用这种模拟来代替被测代码的真实依赖项。

  

在其他文章中建议通过实现接口final来更改被测类的签名,从而无论如何对它进行模拟,从而有可能规避ObjectInput问题。

否。

建议您针对接口进行编程,而不是针对具体类进行编程,并使用依赖注入/控制反转

没有人建议您在测试中的类(您的Server类)实现任何接口。

违反OOP最佳做法

  

问:“为什么不将流传递到服务器?”
  答:因为我在其他任何地方都不需要它们。

     

这确实是最后的努力,如果需要的话,我会的,但是我宁愿不这样做。

我们进行OOP的主要原因之一是代码的可重用性

单元测试是最明显的代码重用。 测试您的代码有困难,这表明缺乏可重用性。 在这一点上,您的Server类根本不打算被“重用”并不重要。 这是您的通用编码方法,将来会让您遇到麻烦。

您的Server类违反了OOP最佳做法:

  • 封装/信息隐藏。

    您最有可能声明了成员变量fromClienttoClient package private ,以便能够从我们的测试中访问它们。 这有两种问题:

    1. 您的代码不应像成员变量一样公开实现细节(除非它是DTO / ValueObject)。
    2. 您永远不应明确修改生产代码以启用测试。
  • 关注点分离

    任何班级(提供业务逻辑)都应具有单一职责。 除了业务逻辑之外,实例化依赖项是完全不同的责任。 因此,您的Server类不应对此实例化负责。

    我知道这有点教条,但是它可以导致更好的可测试性和可重用性的代码。 再说一遍:当前的代码是否 根本不打算重用都没关系。

结论

恕我直言,您真正的问题是:如何在不采用OOP最佳实践的情况下测试我的代码?

在这种情况下,您应该使用 PowerMock 明确地将投降提交至不良设计。