1

In a Junit test, is it possible to test for variables in the main method?

My main method something like this looks like this:

package edu.blah.class.project1;

public class Program {

public static void main(String[] args) {
    try {
        int result = Utility.analyze();
    } catch (Exception e) {
        System.out.println("Error");
    }
    }

}

Is it possible to obtain the variable result in a JUnit class? Is the only way to make it a public variable? Thanks!

O_O
  • 4,397
  • 18
  • 54
  • 69

3 Answers3

3

No, local variables are allocated on stack and are not available outside the method.

Your wishing to check the local variable from test means that you probably should split your method into several smaller ones. Generally this is one of the principals of TDD - Test Driven Development.

AlexR
  • 114,158
  • 16
  • 130
  • 208
3

In a Junit test, is it possible to test for variables in the main method?

Err ... no. Unless you have a JUnit test that calls the main method explicitly, then the main method won't be called in a unit test.

Besides, the variable here is result which is a local variable, and there is no way to reach into a method to test some local variable. (Java simply doesn't allow it ...)

Is it possible to obtain the variable result in a JUnit class?

In this example, result gets the value of the call Utility.analyze(). There is no reason why a JUnit test could not do that too ... and thereby get the same value. (Or at least it could in this example.)

Is the only way to make it a public variable?

No ... see above.

However, if you are actually trying to test the value of result in the context of the main method, then you are right. The only way to get hold of the value would be to expose it as a static variable. It doesn't necessarily have to be public static though. (Indeed, if you were prepared to write do some "nasty" reflection you could even get your JUnit test to extract and test the value of a private static field.)


However, I think you are taking the wrong approach top this. Rather than trying to figure out how to reach into a static method (or any method), it would be better if you restructured the code to make it more testable. For example:

package edu.blah.class.project1;

public class Program {

    public int run(String args[]) {
        return Utility.analyze();
    }

    public static void main(String[] args) {
        try {
            new Program().run(args);
        } catch (Exception e) {
            System.out.println("Error");
        }
    }
}

With a small amount of restructuring (like this), you can make it a lot easier to write unit tests for all of the important functionality. You are left with the "problem" it is still hard to unit test the main method. However, you have reduced the method to the point that it is so simple that you can verify it is correct by visual inspection: unit testing main is now unnecessary.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
1

Unit tests can call any accessible method, including the main method. Apart from it being recognized as an application entry point there is nothing special about the main method.

Regarding how to test your example, in the current state the local variable result cannot be accessed. Before resorting to all sorts of tricks to access it, ask yourself the question why you would want to test for it?

A unit test should check the contract of a method, which comprises of the input parameters, the return value and any side effects. Testable side effects are:

  • changing the state of the encompassing class
  • calling instances of classes that are fields in the class under test

Hard to test side effects are:

  • static calls
  • direct interaction with the environment like the file system, network, threads
  • behavior depending on system time

In most cases the situations that are hard to test can be avoided by refactoring your application, making it more testable.

However, in your example none of the above applies. The local variable 'result' is not used for anything. So in the current state of your application you don't have to (and cannot) test for its value.

When I use my imagination to guess what your application could/should be doing an extended and testable version may look something like this:

public class Program {

    public static final String ERROR_MESSAGE = "Error";

    // Utility modeled as a dependency to avoid static access
    private Utility utility;

    // a poor man's logger, better to use a logging framework like log4j
    private PrintStream logger;

    // 'business logic' extracted into a separate method to be tested
    public void execute(String[] args) {
        try {
            // static access to Utility replaced with instance access
            // passing the args to make testing more interesting
            int result = utility.analyze(args);
            // added logging of the result to make testing more interesting
            logger.println(result);
        } catch (Exception e) {
            // Static access to System.out replaced with logger instance
            logger.println(ERROR_MESSAGE);
        }
    } 

    // setters used for dependency injection
    public void setUtility(Utility utility) {
        this.utility = utility;
    }

    public void setLogger(PrintStream logger) {
        this.logger = logger;
    }

    // application entry point does basic initalization and depency injection
    public static void main(String[] args) {
        // create application instance
        Program program = new Program();

        // inject dependencies
        program.setUtility(new Utility());
        program.setLogger(System.out);

        // call application
        program.execute(args);
    }
}

And the unit test, using JUnit4:

import static org.mockito.Mockito.*;
import org.junit.*;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.runners.MockitoJUnitRunner;
import static org.junit.Assert.*;

@RunWith(MockitoJUnitRunner.class)
public class ProgramTest {

    // @InjectMocks creates an instance and injects any specified mocks
    @InjectMocks
    private Program instance;
    // @Mock creates a mock instance, which can be used to specify behavior using when() and verify access using verify()
    @Mock
    private Utility utility;
    @Mock
    private PrintStream printStream;

    // @Test indicates a test method in JUnit4
    @Test
    public void testExecuteHappy() {

        // SETUP
        String[] args = new String[];
        int expectedResult = 42;
        // specify behavior of the Utility mock to return the expected result
        when(utility.analyze()).thenReturn(expectedResult);

        // CALL
        instance.execute(args);

        // VERIFY
        // check that utility.analyse() was called with the args
        verify(utility).analyze(args);
        // verify that logger.println() was called with the expected result
        verify(logger).println(expectedResult);
    }

    @Test
    public void testExecuteError() {

        // SETUP
        String[] args = new String[];
        // specify behavior of the Utility mock to throw an exception
        when(utility.analyze()).doThrow(new Exception("test exception));

        // CALL
        instance.execute(args);

        // VERIFY
        // check that utility.analyse() was called with the args
        verify(utility).analyze(args);
        // verify that logger.println() was called with the error message
        verify(logger).println(Program.ERROR_MESSAGE);
    }
}

Typically dependencies require additional configuration like which database to access, connection pools etc. In a larger application dependency injection would be done by a framework like Spring or CDI rather than the main method.

Here are the Maven dependencies needed for this:

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>1.9.5</version>
        </dependency>
    </dependencies>

Add these to your pom.xml. If you don't use Maven, download these .jar files and add them to the class path used for compilation:

Note: I didn't compile or test the above code so there might be small typo's

Adriaan Koster
  • 15,870
  • 5
  • 45
  • 60