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