0

So I developing this very simple game of TicTacToe. I want to test the method given below:

public class Board {

    private Scanner scan = new Scanner(System.in);

    public int inputBoardSize() {
        while (flag) {
            System.out.print("Enter the number of grids you want to play with:");
            try {
                boardSize = Integer.parseInt(scan.next());
                if (boardSize < 3 || boardSize > 10) {
                    System.out.println("Please choose a board size between 3 and 10");
                    continue;
                }
                flag = false;
                break;
            } catch (NumberFormatException e) {
                e.getMessage();
                System.out.println("Please enter a number");
                continue;
            }

        }
        printBoard(boardSize);
        return boardSize;
    }

But I am new to unit testing and need a little help. I am not able to figure out how to test two conditions

  1. the NumberFormatException
  2. When the input is not between 3 and 10.

My testing class for the second condition is something like this:

public class BoardTest extends TestCase {

    @Test
    public void test() {
        Board board = new Board();

        String input = "2";
        InputStream in = new ByteArrayInputStream(input.getBytes());
        System.setIn(in);

    }
}

I am not able to figure out what to do next.

Timothy Truckle
  • 15,071
  • 2
  • 27
  • 51
  • How do you test the 3rd condition: the input *is* 3 and 10? – Timothy Truckle Apr 08 '18 at 11:27
  • You need to use what is called [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection). Supply the Scanner instance to your method. In production you can use `System.in` where-as in testing you can use a different `InputStream` that will play your test case. – flakes Apr 08 '18 at 11:29
  • I did use InputStream. Something like this: @Test public void test() { Board board = new Board(); String input = "2"; InputStream in = new ByteArrayInputStream(input.getBytes()); System.setIn(in); } – perpetualcoder Apr 08 '18 at 11:57
  • so what keeps you from creating copies of this test method where you change `"2"` to `"11"` or `"not a number"`? – Timothy Truckle Apr 08 '18 at 12:03

2 Answers2

1

You can extract the validation logic into a separate method and then test that method. This removes the need to interact or inject a Scanner object. The extracted code would resemble

public int inputBoardSize() {
    while (flag) {
        System.out.print("Enter the number of grids you want to play with:");
        validateBoardSize(scan.next());
    }
    printBoard(boardSize);
    return boardSize;
}

protected void validateBoardSize(String input) {

    try {
        boardSize = Integer.parseInt(input);
        if (boardSize < 3 || boardSize > 10) {
            System.out.println("Please choose a board size between 3 and 10");
        }
        else {
            flag = false;
        }
    } 
    catch (NumberFormatException e) {
        e.getMessage();
        System.out.println("Please enter a number");
    }
}

Some JUnit test cases to exercise the validateBoardSize method would be:

public class BoardTest {

    private static final String OUT_OUT_BOUNDS_ERROR_MESSAGE = "Please choose a board size between 3 and 10";
    private static final String NFE_ERROR_MESSAGE = "Please enter a number";
    private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
    private Board board;

    @Before
    public void setUp() {
        System.setOut(new PrintStream(outContent));
        board = new Board();
    }

    @Test
    public void setBoardSizeOf2EnsureErrorMessageDisplayed() {
        board.validateBoardSize("2");
        assertOutOfBoundsErrorMessageDisplayed();
    }

    private void assertOutOfBoundsErrorMessageDisplayed() {
        assertEquals(OUT_OUT_BOUNDS_ERROR_MESSAGE, outContent.toString().trim());
    }

    @Test
    public void setBoardSizeOf3EnsureNoErrorMessageDisplayed() {
        board.validateBoardSize("3");
        assertNoErrorMessageDisplayed();
    }

    private void assertNoErrorMessageDisplayed() {
        assertEquals("", outContent.toString().trim());
    }

    @Test
    public void setBoardSizeOf4EnsureNoErrorMessageDisplayed() {
        board.validateBoardSize("4");
        assertNoErrorMessageDisplayed();
    }

    @Test
    public void setBoardSizeOf9EnsureNoErrorMessageDisplayed() {
        board.validateBoardSize("9");
        assertNoErrorMessageDisplayed();
    }

    @Test
    public void setBoardSizeOf10EnsureNoErrorMessageDisplayed() {
        board.validateBoardSize("10");
        assertNoErrorMessageDisplayed();
    }

    @Test
    public void setBoardSizeOf11EnsureErrorMessageDisplayed() {
        board.validateBoardSize("11");
        assertOutOfBoundsErrorMessageDisplayed();
    }

    @Test
    public void setBoardSizeWithInvalidNumberEnsureInvalidNumberMessageDisplayed() {
        board.validateBoardSize("blah");
        assertInvalidNumberMessageDisplayed();
    }

    private void assertInvalidNumberMessageDisplayed() {
        assertEquals(NFE_ERROR_MESSAGE, outContent.toString().trim());
    }
}

Note that since your program denotes errors through messages sent through standard output (as opposed to throwing exceptions), the tests must intercept the output message sent to standard output and do a string compare to see what the message says. Hence, within setUp(), the OutputStream for standard output is set to an OutputStream instance whose string value can be compared against by the test methods: System.setOut(new PrintStream(outContent)). To test the output, you can just extract the string value of the OutputStream: outContent.toString().trim(). Note that trim() is used to remove the trailing newline character(s), since println will include them in the error message. Newlines are OS-sensitive, so removing them makes comparing the string much more straightforward.

For more information, see JUnit test for System.out.println().

Justin Albano
  • 3,809
  • 2
  • 24
  • 51
  • Thank you so much! This was of great help in understanding how it works. – perpetualcoder Apr 08 '18 at 16:34
  • So I am using System.err instead of System.out. And the unit test is failing org.junit.ComparisonFailure: expected:<[Please enter a number]> but was:<[]>. Is there are a different way to test system.err statements? – perpetualcoder Apr 12 '18 at 11:18
  • There is a way to test standard error instead of standard output. In the `setUp()` method, change `System.setOut(new PrintStream(outContent))` to `System.setErr(new PrintStream(outContent))`. This will allow you to capture and test the output printed to standard error. For more information on how this works, see the [System.setErr documentation](https://docs.oracle.com/javase/9/docs/api/java/lang/System.html#setErr-java.io.PrintStream-). – Justin Albano Apr 12 '18 at 11:25
0

Add the following constructors:

public class Board {

    private Scanner;

    public Board() {
        this(System.in);
    }

    public Board(InputStream in) {
        scan = new Scanner(in);
    }

}

Then you can provide provide an InputStream from the outside when testing the class.

Crusha K. Rool
  • 1,502
  • 15
  • 24