0

PowerMock is a great tool that I have recently started using for testing some static methods. Unfortunately, I am NOT able to re-write anything (apart from the tests), and need PowerMock to be able to test this code strictly as-is.

This is my PowerMock test:

import java.io.*;
import org.junit.*;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;

import org.mockito.runners.MockitoJUnitRunner;
import org.powermock.core.classloader.annotations.PrepareForTest; 

@RunWith(MockitoJUnitRunner.class)
@PrepareForTest({Solution.class})
public class SolutionTest {

    // stream to record the output (System.out)
    private ByteArrayOutputStream testOutput;

    @Before
    public void setUpOutputStream() {
        testOutput = new ByteArrayOutputStream();
        System.setOut(new PrintStream(testOutput));
    }

    // input feed to Scanner (System.in)
    private void setInput(String input) {
        System.setIn(new ByteArrayInputStream(input.getBytes()));
    }

    @Test
    public void test1() {
        // set System.in
        setInput("foo");

        final String expected = "foobar";
        final String actual = testOutput.toString();

        // run the program (empty arguments array)
        Solution.main(new String[0]);

        assertEquals(expected, actual);
    }

    @Test
    public void test2() {
        setInput("new");
        Solution.main(new String[0]);
        final String expected = "newbar";
        final String actual = testOutput.toString();
        assertEquals(expected, actual);
    }
}

PowerMock has made it possible for me run (and pass) two tests in succession on a static method in a scenario such as this:

import java.util.Scanner;
public class Solution {
    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);

        String input = scanner.nextLine();

        scanner.close();

        System.out.print(input + "bar");
    }
}

Before PowerMock, I had been stymied by the exception (caused by having to test static method) java.lang.IllegalStateException: Scanner closed

However, in this alternate scenario, which calls a second static method (also scanner is a static member), that issue re-emerges.

import java.util.Scanner;
public class Solution {

    static void printString(String s) {
        System.out.print(s);
    }

    private static final Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {

        String input = scanner.nextLine();

        printString(input + "bar");

        scanner.close();
    }
}

Here, test1 will pass, but test2 can't even run because of java.lang.IllegalStateException: Scanner closed

I need both tests to pass in the latter scenario, as they do in the former.

For your convenience (and because a tested answer will be most valuable), my dependencies are as follows:

<dependencies>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>1.6.5</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito</artifactId>
        <version>1.6.5</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Thanks very much!

Zack
  • 294
  • 1
  • 10
  • The problem is that `private static final Scanner scanner = new Scanner(System.in)` happens before `setInput("new")`, so when you invoke `scanner.nextLine` you're referring an _old_ `System.in`. The code you are testing (the `Solution` class) is under your control - i.e. you can change its design - or you can't touch it? – Pietro Martinelli Feb 27 '19 at 17:08
  • I have to work with the Solution class as it is. That's my great hurdle here. – Zack Feb 27 '19 at 17:10

2 Answers2

0

I tried something combining mock constructors PowerMock's feature with mock classes (as opposed to mock interfaces) Mockito's feature, but without success: the problem I tried to resolve is that Scanner instance creation happens before setInput invocation, so I've tried with

private static Scanner scannerMock;

    static {
        try {
            scannerMock = Mockito.mock(Scanner.class);
            PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scannerMock);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void setInput(String input) throws Exception {
        PowerMockito.when(scannerMock.nextLine()).thenReturn(input);
    }

This may work with other classes, but not with Scanner class, because it's final. I think there is no solution to your problem when you can't change Solution class a bit: in the past it worked for me an approach like that presented (disclaimer: by me) here, but it is obviously not working without freedom to change Solution's code.

May be you can use reflection to access private static final field Scanner, setting it to a Scanner instance you created before and you can control, as described in the accepted answer of this question: may be it's not the cleaner way to write a test, but I think it can work and solve your problem.

I hope this can help you toward an accaptable and viable solution...

Pietro Martinelli
  • 1,776
  • 14
  • 16
  • A word of caution: static final set through reflection may not be reflected in teh actual code behavior./ –  Feb 27 '19 at 18:22
0

I've run into a problem similar to this. I was trying to create a local testing environment for the HackerRank challenges. My goal is to be able to complete my solution in provided half-finished Solution class, and test it against the test cases downloaded from their website without having to modify the boilerplate codes for every challenge.

In other words, I have a Solution class with codes that I can't (read: don't want to) touch, which includes an input read by a scanner from System.in:

    private static final Scanner scanner = new Scanner(System.in);

I tried to make sure System.in is set to the desired value before that final static scanner instance is created, but as we can see by definition it is by no means easy to modify that scanner in order to customize it for different test cases.

And the other tricky bit is, in Solution class, the output is set up to be written to a file, the location of which is obtained from an environment variable, using System.getenv("OUTPUT_PATH"). This creates an issue because tests can run in parallel and attempt to write results into the same file, as specified by that environment variable.

Long story short, what I ended up doing is to mock the System.class using PowerMock, create a nested class for each of the test cases and add @PrepareForTest for each test case class, and that eventually worked for me. Below is my code for DoAllTest class, which contains all "challenge-specific" information, in this case, it's the "bomberman game" challenge. There are two test cases 00 and 25 for this challenge. It's quite amazing that the SolutionWrap object which contains Solution instance in the following code is actually shared by both tests, but PowerMock takes care of the mocking of System.class and they run as if they're in separate "containers".

package practice.thebombermangame;
import common.SolutionTest;
import common.SolutionTestable;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.junit.experimental.runners.Enclosed;

@RunWith(Enclosed.class)
public class DoAllTest {
    class SolutionWrap implements SolutionTestable {
        public void runMain(String[] args) {
            try {
                Solution s = new Solution();
                s.main(args);
            } catch (IOException e) {
                System.err.println(e.getMessage());
            }
        };
    };

    static SolutionWrap solutionWrap = new DoAllTest().new SolutionWrap();

    @RunWith(PowerMockRunner.class)
    @PrepareForTest({Solution.class, SolutionTest.class, Test1.class})
    public static class Test1 {
        @Test
        public void test1() {
            String testIDString = "00";
            String inputFileName = "src/practice/thebombermangame/input/input" + testIDString + ".txt";
            String outputFileName = "out_path/output" + testIDString  + ".txt";
            String correctFileName = "src/practice/thebombermangame/output/output" + testIDString + ".txt";
            SolutionTest solutionTest = new SolutionTest(inputFileName, outputFileName, correctFileName);
            solutionTest.doTest(solutionWrap);
        }
    };

    @RunWith(PowerMockRunner.class)
    @PrepareForTest({Solution.class, SolutionTest.class, Test2.class})
    public static class Test2 {
        @Test
        public void test2() {
            String testIDString = "25";
            String inputFileName = "src/practice/thebombermangame/input/input" + testIDString + ".txt";
            String outputFileName = "out_path/output" + testIDString  + ".txt";
            String correctFileName = "src/practice/thebombermangame/output/output" + testIDString + ".txt";
            SolutionTest solutionTest = new SolutionTest(inputFileName, outputFileName, correctFileName);
            solutionTest.doTest(solutionWrap);
        }
    };
}


The SolutionTest class is shared by all challenges, System.in and the environment variable get modified in it as shown below:

package common;
import java.io.FileInputStream;
import java.io.IOException;
import org.powermock.api.mockito.PowerMockito;
import org.mockito.Mockito;

public class SolutionTest {
    static String inputFileName;
    String outputFileName;
    String correctFileName;

    public SolutionTest(String inputFileName_, String outputFileName_, String correctFileName_) {
        inputFileName = inputFileName_;
        outputFileName = outputFileName_;
        correctFileName = correctFileName_;
        setSystemIn();
    }

    final static void setSystemIn() {
        try {
            System.out.println("Setting System.in to " + inputFileName);
            System.setIn(new FileInputStream(inputFileName));
        } catch(IOException e) {
            System.err.println(e.getMessage());
        }
    }

    public void doTest(SolutionTestable solutionTestable) {
        PowerMockito.mockStatic(System.class);
        PowerMockito.when(System.getenv(Mockito.eq("OUTPUT_PATH"))).thenReturn(outputFileName);
        SampleTest sampleTest = new SampleTest();
        sampleTest.testMain(solutionTestable, outputFileName, correctFileName);
    }
};

As you can see, setSystemIn() is called when an object of SolutionTest is created and sets System.in to the inputFileName that's passed to the constructor. With System.class being mocked, the scanner object can be set to the desired value.

Fan Zeng
  • 171
  • 5