3

When i try to mock ObjectInputStream object i get a NullPointerException. More precisely, when this line is called: when(inputStream.readObject()).thenReturn(new Person("Doe", "John", "123"));

Code fragment:

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

Normally, when you initialize ObjectInputStream you have to pass an InputStream object as a constructor argument and i bet this is the problem here - there's no InputStream assigned to the ObjectInputStream.

How should i mock the ObjectInputStream properly then?

GhostCat
  • 137,827
  • 25
  • 176
  • 248
sidzej
  • 31
  • 3
  • Unrelated: next time, put up an [mcve]. Just by **leaving out** the ... in your question, and have thenReturn return a simple string for example - you would allow your readers to just copy/paste your code to repro yourself. Dont put anything in your example code that **prevents** users from running it - especially when asking for something that is really about "these lines of code not working". – GhostCat Jul 10 '17 at 14:07
  • Thanks for the quick accept. For the record: besides using other mocking frameworks (which I actually recommend to **not** do) - see my updated answer for another approach. Feel free to ask questions if you need further help (where I am dropping the pen for today). – GhostCat Jul 10 '17 at 14:14
  • It's all good now. Thanks for help. – sidzej Jul 10 '17 at 14:50

3 Answers3

4

This is caused by the fact that readObject() is final.

Mockito can't mock final methods. So what happens is that the attempt to specify that call for the mock object ... simply goes wrong. And yes, the fact that an NPE gets thrown is more than misleading.

You can try to use that experimental feature in Mockito 2 that allows mocking final methods. If that doesn't work for you, PowerMock(ito) or JMockit most likely would do the job.

And just for the record: such subtle problems are yet another good reason to not use Java built-in serialization any more.

The only other way to bypass this: program against the corresponding interface ObjectInput, not the specific implementation class.

As you can easily mock that interface.

But of course, that means changing your production code. Which might not be a bad idea, as it decouples your business logic from the actual form of serialization. If you later choose to serialize as JSON strings, using GSON - you would just replace the implementation of that interface.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
3

As the others stated readObject() is final in ObjectInputStream.

I appreciate that the others did suggest PowerMock to force a mock of this class!

The better solution is to follow that program against interfaces pattern. The method readObject() is declared in the interface ObjectInput implemented by ObjectInputStream So you can change the signature of your class to use the interface ObjectInput rather than the concrete class ObjectInputStream.

And mocking the interface ObjectInput is a piece of cake...


could you add an MCVE of how this is meant to look like? Nobody did this so far. – Tobias Kolb

here it is:

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");
    }   
}

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

I's not me but Mockito having a solution for this:

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");
    }   
}
Timothy Truckle
  • 15,071
  • 2
  • 27
  • 51
  • 1
    Ah, there is an interface for that. Forgot about it! – GhostCat Jul 10 '17 at 14:14
  • could you add an MCVE of how this is meant to look like? Nobody did this so far. – blkpingu Mar 24 '19 at 12:54
  • 1
    @TobiasKolb: done. BTW: yours is response 1'111 on my list... ;o) – Timothy Truckle Mar 24 '19 at 15:12
  • @TimothyTruckle This is beautiful, although 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. – blkpingu Mar 24 '19 at 15:19
  • 1
    @TobiasKolb: you might have added your own question refering this here... – Timothy Truckle Mar 24 '19 at 16:05
  • @TimothyTruckle true. Should I set up my own question? – blkpingu Mar 24 '19 at 16:28
  • Do I absolutely have to pass an OOS to the classes constructor in order to mock it? Because I don't and I would rather initialise it in the class under test (Server), since I don't need it anywhere else. – blkpingu Mar 24 '19 at 16:36
  • https://stackoverflow.com/questions/55326244/how-to-mock-objectoutputstream-writeobject here we go @TimothyTruckle – blkpingu Mar 24 '19 at 16:59
1

You cannot mock a final method as it is not overridable andreadObject() is final :

public final Object readObject(){...}

To achieve your goal, you could refactor your actual code.
For example you could introduce a wrapper class that hold a ObjectInputStream instance and that delegate processing to it.
In this way you could mock the readObject() method of this wrapper class.
You could also use Powermock that provides more features than Mockito but sincerely I avoid using it.

davidxxx
  • 125,838
  • 23
  • 214
  • 215