JMockit With a Static, a Thread, and a Singleton

Working on a legacy Logging class that uses Thread and a custom Log class singleton (that doesn’t even extend slf4j.Logger, but uses it. ummm . . . future refactor); the task was givern to me to test it. Looked in the pom and saw JMockit AND Mockito referenced, and no PowerMock. There were very few tests, less than 1% for the codebase and neither mock libraries had been implemented in any tests. Knowing there were a lot of final and static classes and other nasty code in this old java app JMockit seemed a good choice because Mockito won’t test these without Powermock.

The CustomLogger class in question, that has Thread and the Log singleton was something like this:


public final class CustomLogger {
	public static void logStack()
	{
		try
		{
			Map traces = Thread.getAllStackTraces();
			printStack(traces);
		}
		catch(Error e)  
		{
			//<-- HEADACHE!!!! catches junit assertion errors, 
			// and all stays green even when things are red
		}
	}

	public static String toString(String name, StackTraceElement[] trace)
	{
		StringBuffer buffer = new StringBuffer();
		buffer.append("Thread "+name);
		buffer.append("\n");
		for(StackTraceElement element:trace)
		{
			buffer.append("\tat "+element);
			buffer.append("\n");
		}
		return buffer.toString();
	}

	private static void printStack(Map traces)
	{
		StringBuffer buffer = new StringBuffer();
		for(Thread thread:traces.keySet())
		{
			buffer.append(toString(thread.getName(), traces.get(thread)));
		}
		Log.getInstance().error(buffer.toString()); // the singleton instance
	}
}

A LOT of things going on in here. I decided to mock the Thread and also the underlaying Log singleton. Things get a little complicated. On the Log mock I used the validate() technique — but of course there was a caveat: logStack() has an empty catch block. This means that method catches any JUnit failures. I needed a way around it so I decided to throw an actual high level Exception with a message for a failed condition.

I wrote the tests with JUnit/JMockit on this as such:


public class CustomLoggerTest
{

	private final StackTraceElement[] traces = new StackTraceElement[2];

	private final Map stackTraces = new HashMap();

	private Thread localThread;

	@Before
	public void setUp() throws Exception
	{
		traces[0] = new StackTraceElement("TestClass34", "testMethod34", "testFile34", 34);
		traces[1] = new StackTraceElement("TestClass51", "testMethod51", "testFile51", 51);

		Thread localThread = new Thread();

		stackTraces.put(localThread, traces);
	}

	@After
	public void tearDown() throws Exception
	{
		localThread = null;
	}

	@Test
	public void testLogStack()
	{
		new Expectations()
		{
			@Mocked({ "getAllStackTraces" })
			final Thread unused = null;
			{
				Thread.getAllStackTraces();
				result = stackTraces;
			}
		};

		new Expectations()
		{
			Log log;
			{

				Log.getInstance();  //<-- mocking the singleton
				returns(log);
				log.error(anyString);
				times = 1;
				forEachInvocation = new Object()
				{
					void validate(Invocation inv, String buffer) throws Exception
					{	
						String expected =
								"Thread Thread-1\n\tat TestClass34.testMethod34(testFile34:34)\n\tat " + "TestClass51.testMethod51(testFile51:51)\n";
						System.out.println(expected);	
						System.out.println(buffer);		
					
						if (!expected.equalsIgnoreCase(buffer)) {
							//Assert.fail();  //can't work due to error trapping in method!
							throw new Exception("Assertion Error: Log Output Not As Expected");
						}
					}
				};
			}
		};
		CustomLogger.logStack(); //<-- Custom Logger is static
	}

}

Side note: the test coverage in this article isn't complete this is just the most interesting stuff.

Now about that empty empty catch block; that thing cause me some head scratching. Everytime it went through the assert would throw but the test wouldn't report as RED in a fail situation. The class can't fail properly because it catches a failed assertEquals. The mechanism in JUnit throws a java.lang.AssertionError or a junit.framework.AssertionFailedError -- trapped by the empty catch block. The best thing to do would be to fix the code (haha), but those weren't the marching orders. Please don't get me started.

This goes to show you -- when you are writing codde do NOT ever make *any* assumptions about seemingly harmless methods. You can get caught inheriting a methoud you may never think gets called (like an equals or hash method_ and it does.

Now about JMockit. It solved my problem, but I find the documentation not too friendly and the syntax is barely readable to me. I much, much prefer Mockito. I am not sure what I think about a mixed JMockit/Mockito application; I made the decision not to do so on that code base. But in the future might rethink that -- unit tests should be terse and readable in my opinion, not so cleaver as to require a lot of refactoring. Self contained per each test is possible, with few harnasses if possible. This makes things like onboarding and code maintenance MUCH easier. Who cares, as long as the tests run quickly and they give you meaningful, readable coverage? They are, after all, the business requirements especially in TDD is a shop's chosen method.

No Comments

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>