23

I have to test a method in a class which takes an input using Scanner class.

package com.math.calculator;

import java.util.Scanner;

public class InputOutput {

    public String getInput() {
        Scanner sc = new Scanner(System.in);
        return sc.nextLine();
    }
}

I want to test it using JUnit but not sure how to do it.

I tried using the following code but it wont work.

package com.math.calculator;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class InputOutputTest {

    @Test
    public void shouldTakeUserInput() {
        InputOutput inputOutput= new InputOutput();

        assertEquals("add 5", inputOutput.getInput());
    }
}

I want to also try it with Mockito (using mock... when ... thenReturn) but not sure how to do it.

1

5 Answers 5

38

You can change the System.in stream using System.setIn() method.

Try this,

@Test
public void shouldTakeUserInput() {
    InputOutput inputOutput= new InputOutput();

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

    assertEquals("add 5", inputOutput.getInput());
}

You have just modified the System.in field. System.in is basically an InputStream which reads from the console (hence your input in the console). But you just modified it and let the system to read from the provided inputstream instead. So it wont read from console anymore but from the inputstream provided.

Sign up to request clarification or add additional context in comments.

2 Comments

it worked :). Could you please tell me how this works?
Excellent solution
9

You can write a clear test for the command line interface by using the TextFromStandardInputStream rule of the System Rules library.

public void MyTest {
  @Rule
  public final TextFromStandardInputStream systemInMock
    = emptyStandardInputStream();

  @Test
  public void shouldTakeUserInput() {
    systemInMock.provideLines("add 5", "another line");
    InputOutput inputOutput = new InputOutput();
    assertEquals("add 5", inputOutput.getInput());
  }
}

2 Comments

By far the best method, though it requires system-rules
@Stefan your solution works very well, add this dependency and you are good to go! <dependency> ` <groupId>com.github.stefanbirkner</groupId>` <artifactId>system-rules</artifactId> <version>1.16.0</version> </dependency>
4

In addition to switching System.in, as Codebender also mentioned, consider refactoring so getInput() becomes a one-line call to a thorough getInput(Scanner) method you write, which you could easily test by creating your own Scanner("your\ntest\ninput\n"). There are a number of other ways to inject your scanner dependency, like making a field you overwrite for testing, but just making a method overload is extremely easy and technically gives you more flexibility (letting you add a feature to read input from a File, for instance).

In general, remember to design for ease of testing, and test the high-risk parts more heavily than the low-risk parts. This means that refactoring is a good tool, and that testing getInput(Scanner) is likely much more important than testing getInput(), especially as you do more than just calling nextLine().

I would recommend heavily against creating a mock Scanner: Not only is it bad practice to mock a type you don't own, but Scanner represents a very large API of interrelated methods where call order matters. To replicate it in Mockito means that either you would create a big fake Scanner implementation in Mockito or mock a minimal implementation that tests only the calls you make (and breaks if your implementation changes, even if your changes provide a correct result). Use a real Scanner and save Mockito practice for external service calls or cases where you're mocking a small yet-unwritten API you define.

Comments

2

I have solved the same problem with help from Mockito's mockStatic mechansim.

Created a class like below:

public class PromptProvider {

public static String provide() {
        return new Scanner(System.in).nextLine();
    }
}

Used at like below when I need input from user:

String input = PromptProvider.provide();

Finally at unit tests:

static MockedStatic<PromptProvider> mockedStatic;

@BeforeClass
public static void setUp() {
    mockedStatic = Mockito.mockStatic(PromptProvider.class);
}

@AfterClass
public static void afterAll() {
    mockedStatic.close();
}

@Test
public void testRefactor() {
    // provide consecutive user inputs like below
    mockedStatic.when(PromptProvider::provide).thenReturn("1", "E");
}

Comments

-2

First of all I assume that the objective of your test is to verify that user input is obtained from the scanner and that the value returned is what has been input in the scanner.

The reason why you mocking does not work is because you are creating the actual scanner object every time within the getInput() method. Hence no matter what you do your mockito instance is never called. Hence the correct way to make this class testable would be to identify all the external dependencies for the class (in this case the java.util.Scanner and inject them into the class through the constructor. This way you can inject the mock Scanner instance during testing. This is a basic step towards dependency injection which in turn leads to good TDD. An example would help you:

 package com.math.calculator;

    import java.util.Scanner;

    public class InputOutput {

        private final Scanner scanner;

        public InputOutput()
        {
           //the external exposed default constructor 
           //would use constructor-chaining to pass an instance of Scanner.

           this(new Scanner(System.in));
        }

        //declare a package level constructor that would be visible only to the test class. 
      //It is a good practice to have a class and it's test within the same     package.
        InputOutput(Scanner scanner)
        {
            this.scanner  = scanner;
        }

        public String getInput() {

            return scanner.nextLine();
        }
    }

Now your test method:

@Test
public void shouldTakeUserInput() {
    //create a mock scanner
    Scanner mockScanner = mock(Scanner.class);
    //set up the scanner
    when(mockScanner.nextLine()).thenReturn("add 5");

    InputOutput inputOutput= new InputOutput(mockScanner);

    //assert output
    assertEquals("add 5", inputOutput.getInput());

   //added bonus - you can verify that your scanner's nextline() method is
   //actually called See Mockito.verify
   verify(mockScanner).nextLine();
}

Also note that since in the above class I am injecting using a constructor, I have declare the Scanner instance final. Since I have no more mutable state in this class this class is thread-safe.

The concept of constructor-based dependency injection is pretty cool and worth reading up on the internet. It helps a big way to develop good thread-safe testable code.

1 Comment

Scanner cannot be mocked because the class is final.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.