package cs3500.connectn.consoleui; import cs3500.connectn.consoleui.ConsoleView.Messages; import cs3500.connectn.model.Model; import cs3500.connectn.model.Player; import org.junit.Test; import java.io.*; import java.util.Formatter; import static org.junit.Assert.assertEquals; /** * Integration tests for the whole program. We avoid mocking the view, model, * and int reader, and instead mock only stdin and stdout so that we can * provide input to the program and check its output. */ public class IntegrationTest { Model model = Model.builder().width(2).height(3).goal(3).build(); // The arguments to testRun() constitute a small, embedded language // designed to clearly represent console interactions of the sort we are // testing. It may look complicated, but all it does is assemble two large // strings, one the input to send to the program under test, and the other // the output to expect. The rest is sugar to make the example itself // easier to follow. @Test public void testRun1() throws IOException { testRun(model, prints("| | |", "| | |", "| | |", "+-+-+", " 0 1"), prompts("White: ", "0"), prints("| | |", "| | |", "|W| |", "+-+-+", " 0 1"), prompts("Red: ", "1"), prints("| | |", "| | |", "|W|R|", "+-+-+", " 0 1"), prompts("White: ", "0"), prints("| | |", "|W| |", "|W|R|", "+-+-+", " 0 1"), prompts("Red: ", "1"), prints("| | |", "|W|R|", "|W|R|", "+-+-+", " 0 1"), prompts("White: ", "0"), prints("|W| |", "|W|R|", "|W|R|", "+-+-+", " 0 1"), formats(Messages.GAME_OVER), formats(Messages.WINNER, Player.White)); } @Test public void testRun2() throws IOException { // "0\n1\n1\n0\n0\n1\n", testRun(model, prints("| | |", "| | |", "| | |", "+-+-+", " 0 1"), prompts("White: ", "0"), prints("| | |", "| | |", "|W| |", "+-+-+", " 0 1"), prompts("Red: ", "1"), prints("| | |", "| | |", "|W|R|", "+-+-+", " 0 1"), prompts("White: ", "8"), formats(Messages.COLUMN_OOB, 1), prompts("White: ", "one"), formats(Messages.NOT_A_NUMBER), prompts("White: ", "1"), prints("| | |", "| |W|", "|W|R|", "+-+-+", " 0 1"), prompts("Red: ", "0"), prints("| | |", "|R|W|", "|W|R|", "+-+-+", " 0 1"), prompts("White: ", "0"), prints("|W| |", "|R|W|", "|W|R|", "+-+-+", " 0 1"), prompts("Red: ", "0"), formats(Messages.COLUMN_FULL, 0), prompts("Red: ", "1"), prints("|W|R|", "|R|W|", "|W|R|", "+-+-+", " 0 1"), formats(Messages.GAME_OVER), formats(Messages.STALEMATE)); } /** * An interaction with the user consists of some input to send the program * and some output to expect, but we represent it as an object that appends * the input and output to build each string. This makes it easier to avoid * expensive string concatenation. */ interface Interaction { void apply(StringBuilder in, StringBuilder out); } /** * Constructs an interaction in which the program produces some output * specified as a sequence of lines. The lines will have terminating newlines * added, so they should not include newlines unless extra are required. * * @param lines the sequence of lines output by the program * @return the new interaction */ static Interaction prints(String... lines) { return (input, output) -> { for (String line : lines) { output.append(line).append('\n'); } }; } /** * Constructs an interaction in which the program produces some output * specified using printf-style format string interpolation. * * @param template the template, as in {@link Formatter} * @param params the positional parameter to interpolate * @return the new interaction */ static Interaction formats(String template, Object... params) { return (input, output) -> { output.append(String.format(template, params)); }; } /** * Constructs an interaction in which the program prompts the user and * then accepts some input. * * @param prompt the expected prompt * @param response the user input to simulate in response * @return the new interaction */ static Interaction prompts(String prompt, String response) { return (input, output) -> { output.append(prompt); input.append(response).append('\n'); }; } /** * Tests a game run on the given model using the given sequence of user * interactions. * * @param model the model to run the test with * @param interactions user I/O interactions * @throws IOException if there's an IO error */ void testRun(Model model, Interaction... interactions) throws IOException { StringBuilder fakeUserInput = new StringBuilder(); StringBuilder expectedOutput = new StringBuilder(); for (Interaction interaction : interactions) { interaction.apply(fakeUserInput, expectedOutput); } InputStream input = new ByteArrayInputStream(fakeUserInput.toString().getBytes("UTF8")); StringBuilder actualOutput = new StringBuilder(); Controller controller = new Controller(model, input, actualOutput); controller.run(); assertEquals(expectedOutput.toString(), actualOutput.toString()); } }