2

When trying to mock console input, the test doesen't behave as i expected

I created a wrapper class for console input, output, and tried mocking its behaviour

public class ConsoleReaderWriter {

public void printLine(String message) {

    System.out.println(message);
}

public String readLine() {
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
    String result = "";
    try {
        result = bufferedReader.readLine();
    } catch (IOException e) {
        System.err.print(e);
    }
    return result;

}

}

//the method to be tested

public String readPlayerName() {
    consoleReaderWriter.printLine("> What is your name?");
    String playerName = consoleReaderWriter.readLine();
    return playerName;
}

//my test attempt

@Test
public void testReadPlayerNameShouldReturnNameString() {
    String testName = "John Doe";

    ConsoleReaderWriter testReaderWriter = mock(ConsoleReaderWriter.class);

    when(testReaderWriter.readLine()).thenReturn("John Doe");

    assertEquals(testName, underTest.readPlayerName());
}

I am using Mockito. When i run the test, it prompts me to enter input from the console. The test passes, if i enter the expected name, however, i would like to make it automatic, so that i dont have to enter any input while the test runs. Thanks in advance.

Gal Dreiman
  • 3,969
  • 2
  • 21
  • 40
Mangos
  • 147
  • 1
  • 7
  • What does `underTest.readPlayerName()` mean? Your mocked object calls `testReaderWriter` as far as I see, so you assert should looks like `assertEquals(testName, testReaderWriter.readPlayerName())` I guess. – dmitrievanthony Apr 10 '19 at 11:53
  • 1
    You need to inject your mock object into tested object (```underTest```). – Peteef Apr 10 '19 at 11:54
  • testReaderWriter is a wrapper class to handle console input and output, underTest is the Class that i am writing tests for. It also handles consoleIO, but on a higher level. It has a method readPlayerName(), which method then calls for the wrapper class to read the string. – Mangos Apr 10 '19 at 11:56
  • @Peteef Should i rewrite the constructor of underTest to accept a wrapper class when i instanciate it? – Mangos Apr 10 '19 at 12:13
  • You can also try to use ```@Mock``` and ```@InjectMocks``` annotations, as I described below. – Peteef Apr 10 '19 at 12:18

3 Answers3

3

Please take a look at the example:

@RunWith(MockitoJUnitRunner.class)
public class TestClass {
   @Mock
   ConsoleReaderWriter crw;

   @InjectMocks
   UnderTestClass underTest;

   //Some other fields

   @Test
   public void testReadPlayerNameShouldReturnNameString() {
      String testName = "John Doe";

      when(crw.readLine()).thenReturn("John Doe");

      assertEquals(testName, underTest.readPlayerName());
   }
}
Peteef
  • 377
  • 1
  • 2
  • 10
  • 1
    I don't think so. I see no ```readPlayerName()``` inside ```ConsoleReaderWriter```. – Peteef Apr 10 '19 at 12:08
  • One thing I don't like about @InjectMocks is that it will work even if the member in the class under test is private, creating a mismatch between the way the class under test can be accessed from a test, and the way the class can be accessed from a real production client. – Gonen I Apr 10 '19 at 12:21
1

Here:

ConsoleReaderWriter testReaderWriter = mock(ConsoleReaderWriter.class);

Thing is: that specific testReaderWriter should be used by your object under test. Right now, you are creating a mocked object, that is no way related to your object you intend to verify. So, you can follow the advice given in the other answers and make sure your mock gets actually passed to the object under test.

But a better approach would be: to decouple your production code from the input it is using. Your problems start here:

new InputStreamReader(System.in)

You are creating a reader based on a fixed input. Don't do that.

Instead, you could for example pass an instance of InputStream to your ConsoleReaderWriter object. And then that stream is used for all input. Then you don't need to mock anything, you just prepare a stream with fixed content, and you give that to your object under test.

Another way is to mess with System.in, as outlined here for example.

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

You need to inject your mocked object into your underTest instance.

In the UnderTest class itself, make sure you don't use 'new' to create your ConsoleReaderWriter dependency, but accept it from the outside instead, for example using a constructor agrument.

It could be something along the lines of:

@Test
public void testReadPlayerNameShouldReturnNameString() {
    String testName = "John Doe";


    ConsoleReaderWriter testReaderWriter = mock(ConsoleReaderWriter.class);
    when(testReaderWriter.readLine()).thenReturn("John Doe");

    UnderTest underTest = new UnderTest(testReaderWriter);            
    assertEquals(testName, underTest.readPlayerName());
}
Gonen I
  • 5,576
  • 1
  • 29
  • 60