DataOutputStream mock抛出的空指针异常

时间:2014-02-21 10:49:38

标签: java unit-testing mockito

我正在为包装TCP套接字的类编写单元测试(使用Mockito作为我的模拟库)。问题是任何在DataOutputStream上调用writeBytes的测试都会抛出空指针异常。

我在下面提供了三条信息:

1)被测试的课程,TCPClient

2)单元测试,TCPClientTest。失败的测试是testSendWhileActive()和testSendWhileInactive()

3)空指针异常的堆栈跟踪

我在调试器中运行测试并验证传递给dataOutStreamFact.get的参数不是null,也不是它的返回。我在下面列出了两个标识符:

OutputStream $$ EnhancerByMockitoWithCGLIB $$ 344932e5(id = 74)

mOutStream DataOutputStream $$ EnhancerByMockitoWithCGLIB $$ 4499185f(id = 114)

一件看似不寻常的事情是NPE的来源。它发生在第276行的DataOutputStream.java中,它尝试写入底层输出流。我只能假设如果我的模拟是正确创建的,我们就不会在DataOutputStream中执行任何代码。

正在测试的类:TCPClient

package com.plined.arenavideos;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

public class TCPClient extends Thread {

    PacketListener mListener;
    boolean mActive;
    Socket mSocket;
    DataOutputStream mOutStream;
    BufferedReader mInStream;

    public TCPClient(String ip, int port, PacketListener listener, 
            SocketFactory sockFact, DataOutputStreamFactory dataOutStreamFact,
            BufferedReaderFactory buffReadFact) throws IOException {
        mActive = true;
        mSocket = sockFact.get(ip, port);
        mOutStream = dataOutStreamFact.get(mSocket.getOutputStream());
        mInStream = buffReadFact.get(mSocket.getInputStream());
        mListener = listener;
    }

    /*
     * Deactivates this tcp client so it no longer
     * sends and listens to packets.
     */
    public void deactive() {
        mActive = false;
    }

    /*
     */
    public void run() {
        beginListening();
    }

    /*
     * Begins listening for packets from the socket
     * and passes them on to listeners when found.
     */
    void beginListening() {
        while(true) {
            getPacket();
        }
    }

    void getPacket() {
        try {
            String input = mInStream.readLine();

            if (input == null) {
                //TODO: Make it notify the manager here instead. SO it can cleanup.
                throw new RuntimeException("Connection to master has died.");
            }

            if (mActive) {
                System.out.println("Received data from master " + input);
                mListener.processPacket(input);
            }
        } catch (IOException e) {
            System.out.println("Error while receiving from stream");
        }
    }


    /*
     * Sends the provided data using the TCP socket.
     */
    public void send(String data) throws IOException {
        if (mActive) {
            mOutStream.writeBytes(data);
        }
    }

    public interface IFactory {
        public TCPClient get(String ip, int port);
    }

    public interface SocketFactory {
        public Socket get(String ip, int port);
    }

    public interface DataOutputStreamFactory {
        public DataOutputStream get(OutputStream stream);
    }

    public interface BufferedReaderFactory {
        public BufferedReader get(InputStream stream);
    }

}

单元测试:TCPClientTest

package com.plined.arenavideos;

import static org.mockito.Mockito.*;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

import com.plined.arenavideos.TCPClient.BufferedReaderFactory;
import com.plined.arenavideos.TCPClient.DataOutputStreamFactory;
import com.plined.arenavideos.TCPClient.SocketFactory;

public class TCPClientTest {

    Socket mockSocket;
    DataOutputStream mockDataOutStream;
    BufferedReader mockBufferedReader;

    SocketFactory mockSocketFactory;
    DataOutputStreamFactory mockDataOutputStreamFactory;
    BufferedReaderFactory mockBufferedReaderFactory;

    PacketListener mockListener;

    TCPClient clientUnderTest;

    static final String packetData = "newJob^20^/home/perry/work/test.mp4^/home/perry/work/test_converted.mp4\n";

    @Before
    public void setup() throws IOException {
        //Create our factory returns
        mockSocket = mock(Socket.class);
        when(mockSocket.getOutputStream()).thenReturn(mock(OutputStream.class));
        when(mockSocket.getInputStream()).thenReturn(mock(InputStream.class));

        mockDataOutStream = mock(DataOutputStream.class);

        mockBufferedReader = mock(BufferedReader.class);

        mockSocketFactory = mock(SocketFactory.class);
        when(mockSocketFactory.get(anyString(), anyInt())).thenReturn(mockSocket);

        mockDataOutputStreamFactory = mock(DataOutputStreamFactory.class);
        when(mockDataOutputStreamFactory.get(any(OutputStream.class))).thenReturn(mockDataOutStream);

        mockBufferedReaderFactory = mock(BufferedReaderFactory.class);
        when(mockBufferedReaderFactory.get(any(InputStream.class))).thenReturn(mockBufferedReader);

        mockListener = mock(PacketListener.class);

        clientUnderTest = new TCPClient("52.50.30.25", 25200, mockListener, mockSocketFactory, mockDataOutputStreamFactory, mockBufferedReaderFactory);
    }

    @Test
    public void testGetPacketPassToListener() throws IOException {
        when(mockBufferedReader.readLine()).thenReturn(packetData);
        clientUnderTest.getPacket();
        verify(mockListener).processPacket(packetData);
    }

    @Test
    public void testGetPacketDontPassAsInactive() throws IOException {
        when(mockBufferedReader.readLine()).thenReturn(packetData);
        clientUnderTest.deactive();
        clientUnderTest.getPacket();
        verify(mockListener, never()).processPacket(packetData);
    }

    @Test(expected=RuntimeException.class)
    public void testGetPacketFailedConnection() throws IOException {
        String packetData = null;
        when(mockBufferedReader.readLine()).thenReturn(packetData);
        clientUnderTest.getPacket();
    }


    //TODO: Fix this test.
    //@Test
    public void testSendWhileActive() throws IOException {
        clientUnderTest.send(packetData);
        verify(mockDataOutStream).writeBytes(packetData);
    }

    //TODO: Fix this test.
    //@Test
    public void testSendWhileInactive() throws IOException {
        clientUnderTest.deactive();
        clientUnderTest.send(packetData);
        verify(mockDataOutStream, never()).writeBytes(packetData);
    }
}

testSendWhileActive的堆栈跟踪

java.lang.NullPointerException
    at java.io.DataOutputStream.writeBytes(DataOutputStream.java:276)
    at com.plined.arenavideos.TCPClient.send(TCPClient.java:77)
    at com.plined.arenavideos.TCPClientTest.testSendWhileActive(TCPClientTest.java:86)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

1 个答案:

答案 0 :(得分:2)

预计,Mockito无法模拟最终方法或类,java.io.DataOutputStream.writeBytes(...)是最终的。 DataOutputStream课程不是final且同时具有final和非final public方法,因此Mockito在创建模拟时不会发出警告是合理的。

同样作为mockito提交者之一,我强烈建议你不要模拟你不拥有的类型。这有几个很好的理由。其中一个是如果你嘲笑某些东西,并且库调整或只是改变你的测试无法检测到的行为,这个代码可能在生产中失败,即在最糟糕的时刻!

相反,你应该通过声明预期的数据来重写这个测试。

HTH