junit testing for user input using Scanner

前端 未结 4 881
终归单人心
终归单人心 2020-12-06 01:04

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 InputOutpu         


        
4条回答
  •  粉色の甜心
    2020-12-06 01:47

    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.

提交回复
热议问题