3
\$\begingroup\$

This code allows calling a method while redirecting the IO to strings. This is useful for unit testing where you have to direct something to the console or examine output that comes from it. I assume these have been written a lot, but I couldn't find examples.

I'd like advice on how to make them generally useful and whether the style seems decent. There are two methods-- one sends a string to System.in while leaving the output alone, the other redirects both input and output, with the option that if the input string passed is null, it leaves the input alone. This covers all three cases.

Invocations look like this:

enterInput(inputString, callingClassString, methodString, typeStrings[], argsForCall ...);
captureIO(inputString, callingClassString, methodString, typeStrings[], argsForCall ...);

Code:

/**
 * Invoke the specified method using the specified string as console input.
 * Note that the calling class and types must be fully qualified.
 * 
 * @param inputString               Console input to be fed to the method
 * @param callingClassString        Class of the method to invoke
 * @param method                    Name of the method to invoke
 * @param typeStrings               Types the method takes (to distinguish it)
 * @param args                      Arguments to pass to the method
 * @return                          The console output captured
 */
public static void enterInput(String inputString,
        String callingClassString, String method, String[] typeStrings,
        Object... args) {
    try {
        Class<?>[] typeClasses = new Class<?>[typeStrings.length];
        for (int i = 0; i < typeStrings.length; i++) {
            typeClasses[i] = Class.forName(typeStrings[i]);
        }

        Method m = Class.forName(callingClassString).getMethod(method, typeClasses);
        InputStream origIn = System.in;
        System.setIn(new ByteArrayInputStream(inputString.getBytes()));
        m.invoke(null, args);
        System.setIn(origIn);
    } catch (Exception e) {
        throw new Error(e);
    }
}

/**
 * Invoke the specified method and return its console output as a string.
 * If inputString is not null, it is used as console input.
 * Note that the calling class and types must be fully qualified.
 * 
 * @param inputString               Console input to be fed to the method.
 *                                  If null, leaves this input stream alone
 * @param callingClassString        Class of the method to invoke
 * @param method                    Name of the method to invoke
 * @param typeStrings               Types the method takes (to distinguish it)
 * @param args                      Arguments to pass to the method
 * @return                          The console output captured
 */
public static String captureIO(String inputString, String callingClassString, String method, 
        String[] typeStrings, Object ... args) {
    String output = null;

    try {
        Class<?>[] typeClasses = new Class<?>[typeStrings.length];
        for (int i=0; i<typeStrings.length; i++) {
            typeClasses[i] = Class.forName(typeStrings[i]);
        }

        Method m = Class.forName(callingClassString).getMethod(method, typeClasses);
        InputStream origIn = System.in;
        PrintStream origOut = System.out;

        if (inputString != null)
            System.setIn(new ByteArrayInputStream(inputString.getBytes()));
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        System.setOut(new PrintStream(baos));
        m.invoke(null, args);
        output = baos.toString();

        System.setOut(origOut);
        if (inputString != null)
            System.setIn(origIn);
    } catch (Exception e) {
        throw new Error(e);
    }
    return output;
}
\$\endgroup\$
1
  • \$\begingroup\$ I think the code you try to test with these methods would also benefit from a review. If you have a method that prints some output that needs to be tested than pass in the System.out as a parameter. You can then pass in your byte output stream when you want to test. \$\endgroup\$ Jan 17, 2013 at 12:13

2 Answers 2

2
\$\begingroup\$

This (and reflection in general) completely breaks refactoring, static code analysis and many more things and should therefore be avoided. (Imagine what happens if you find out a better naming for a function or you change the arguments.)

If you really need this behavior, you could use (I assume jUnit 4+) hooks before and after class/test case:

@BeforeClass
public static void setUpBeforeClass() throws Exception {
}

@AfterClass
public static void tearDownAfterClass() throws Exception {
}

@Before
public void setUp() throws Exception {
}

@After
public void tearDown() throws Exception {
}

and do whatever is necessary.
Besides this, as already written, I would suggest to modify your methods, too. It could be a better approach to separate the view (prints) from model/controller (functions doing something).

\$\endgroup\$
1
  • \$\begingroup\$ +1 for recognizing that OP was trying to reinvent some functionality of JUnit. \$\endgroup\$ Jan 23, 2013 at 12:16
1
\$\begingroup\$

In any case you should add the resetting of the in and out in the finally block, otherwise you will have problem in case an exception is thrown.


Nevertheless, did you ever thought of using Log4J or any other framework for logging? There you can easily define an Appender and decide where the log message will be written to.

In addition to that, your tests shouldn't check the logging. (Except you are testing the logging.) Try to create smaller method, where you can easily test the return values instead of the log output.

\$\endgroup\$
5
  • \$\begingroup\$ you should add the resetting of the in and out in the finally block, otherwise you will have problem in case an exception is thrown. There is a System.exit(1), as long as there is only one thread, it does not matter. But the statement is still true in the general case. \$\endgroup\$
    – tb-
    Jan 21, 2013 at 18:40
  • \$\begingroup\$ You are right, I missed that. \$\endgroup\$ Jan 21, 2013 at 20:01
  • \$\begingroup\$ Throwing an Error directly is even worst than throwing a raw Exception. \$\endgroup\$ Jan 23, 2013 at 4:36
  • \$\begingroup\$ By throwing the error, I mean to be doing the equivalent of System.exit(), but just for this one program. System.exit() kills the JVM. What should I be doing? \$\endgroup\$
    – pinecone
    Jan 27, 2013 at 19:03
  • \$\begingroup\$ Throw an IOException and handle the program exit at the calling location, not in your library. \$\endgroup\$ Jan 28, 2013 at 4:49

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.