JUnit

Parameterized JUnit test

Today I needed to do a quick test for a custom NameUtils class that capitalized human and business names based on some very specific rules. To do that I decided that the best way is to use a Parameterized JUnit test and the best and fastest way of doing it is by using Junit with JUnitParams. This project adds a new runner to JUnit and provides much easier and readable parameterized tests for JUnit >=4.6. According to their own JUnitParams homepage the main differences to standard JUnit Parameterized runner are:

  • more explicit – params are in test method params, not class fields
  • less code – you don’t need a constructor to set up parameters
  • you can mix parameterised with non-parameterised methods in one class
  • params can be passed as a CSV string or from a parameters provider class
  • parameters provider class can have as many parameters providing methods as you want, so that you can group different cases
  • you can have a test method that provides parameters (no external classes or statics anymore)
  • you can see actual parameter values in your IDE (in JUnit’s Prameterized it’s only consecutive numbers of parameters)

So first, since we are using maven, then we need our dependencies:


<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.11</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>pl.pragmatists</groupId>
	<artifactId>JUnitParams</artifactId>
	<version>1.0.2</version>
	<scope>test</scope>
</dependency>


While JUnit is very common, JunitParams is probably not so popular yet. It’s a package of small helper annotations that allow you to better write your tests.

So now – what are these parametrized tests I’m talking about? Here’s a quick example:

import static org.junit.Assert.assertEquals;
import junitparams.FileParameters;
import junitparams.JUnitParamsRunner;

import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JUnitParamsRunner.class)
public class NameUtilsTest {

	@Test
	@FileParameters("src/test/resources/NameUtils.test.csv")
	public void testNameCapitalization(String input, String expected) {
		
		assertEquals(expected, NameUtils.capitalize(input));
		
	}

}

This test asserts that our NameUtils.capitalize method returns expected results.
As you can see, we use @RunWith that tells JUnit to this test using JUnitParamsRunner and then we use @FileParameters that tells junit to run this test once for every single line in that CSV file.

On each of the CSV file lines we have our input and expected output, like this:

john doe, 					John Doe
DR. JOHN DOE, 				Dr. John Doe
ACME Corporation Ltd.,		Acme Corporation Ltd.
Miriam Torrence, 			Miriam Torrence
Shawanna Melody, 			Shawanna Melody
    SPACE     Tuma, 		Space Tuma
Jeraldine Ownbey, 			Jeraldine Ownbey
Nilda Cavaliere, 			Nilda Cavaliere
bUffy Yearta, 				Buffy Yearta,
AMANDA skoog, 				Amanda Skoog
a,							A
a b			, 				A B
Elias johnny cash Rash,		Elias Johnny Cash Rash

And now when we run our code, we get:
parameterized junit test results

Note that the test was ran for every single line in that CSV file and this allows you to quickly add new cases or testing material to constantly improve your code verifications.

If you have a question or having trouble getting it to work on your machine then feel free to write in the comment section and I’ll do my best to help you out. If you would like to read more about Unit Testing related subjects then also make sure to comment on that and if you liked what you read then make sure to subscribe – It’s FREE!

Struts2 Convention & Spring Junit4

The problem

You normally would use StrutsSpringJunit4TestCase as the base class to unit-test your actions. But this class does not support Struts2 Convention plugin Annotations. So if you are like me and you like to keep your actions and results defined in the Action classes by annotations you are s***-out-of-luck. Most likely on your first attempt you will see exceptions like this:

WARN org.springframework.mock.web.MockServletContext.getResourcePaths:212 – Couldn’t get resource paths for class path resource [WEB-INF/content/]¬†java.io.FileNotFoundException: class path resource [WEB-INF/content/] cannot be resolved to URL because it does not exist. This effectively means that the conventions plugin is attempting to locate the resources etc.. Now the simple solution to resolve all those warnings is to modify your struts.xml file and add this setting in there:

<constantname=“struts.convention.result.path”value=“/”/>

Ok, so the warnings are gone but non of your annotated struts actions have actually been loaded into the configuration and you can’t unit-test them. So what gives?

The Solution – Almost

Now, here’s a nice solution almost:¬†http://depressedprogrammer.wordpress.com/2007/06/18/unit-testing-struts-2-actions-spring-junit/

I do want to credit Zarar Siddiqi for that class mostly. It’s a good starting base, but needs a bit of customization for the latest Struts / Spring and Junit 4 environment.

The Solution – Finally

Here’s the source code for the class i created based on the above posting:

import java.util.HashMap;

import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.mapper.ActionMapper;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.views.JspSupportServlet;
import org.junit.Before;
import org.junit.BeforeClass;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ContextLoader;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ActionProxyFactory;

/**
 * Improved the sample from originally
 * http://depressedprogrammer.wordpress.com/2007/06/18/unit-testing
 * -struts-2-actions-spring-junit/
 * 
 * - Added support for Struts Convention plugin annotations, also made it run
 * with JUnit 4.
 * 
 * @author Zarar Siddiqi
 * @author codeyouneed.com
 * 
 */
public abstract class StrutsConventionSpringJUnit4TestCase {

	/**
	 * Where are the Spring configurations?
	 */
	private static final String CONFIG_LOCATIONS = "applicationContext-test.xml,"
			+ "applicationContext-test-security.xml";

	private static ApplicationContext applicationContext;
	protected Dispatcher dispatcher;
	protected ActionProxy proxy;
	protected static MockServletContext servletContext;
	protected static MockServletConfig servletConfig;
	protected MockHttpServletRequest request;
	protected MockHttpServletResponse response;

	/**
	 * Mimic the getActionProxy method the usual StrutsSpringJuni4TestCase uses.
	 * 
	 * @param clazz
	 *          Class for which to create Action
	 * @param namespace
	 *          Namespace of action
	 * @param name
	 *          Action name
	 * @return Action class
	 * @throws Exception
	 *           Catch-all exception
	 */
	@SuppressWarnings("unchecked")
	protected ActionProxy getActionProxy(String uri) {
		request.setRequestURI(uri);
		ActionMapping mapping = Dispatcher
				.getInstance()
				.getContainer()
				.getInstance(ActionMapper.class)
				.getMapping(request, Dispatcher.getInstance().getConfigurationManager());
		String namespace = mapping.getNamespace();
		String name = mapping.getName();
		String method = mapping.getMethod();

		// create a proxy class which is just a wrapper around the action call.
		// The proxy is created by checking the namespace and name against the
		// struts.xml configuration
		proxy = dispatcher
				.getContainer()
				.getInstance(ActionProxyFactory.class)
				.createActionProxy(namespace, name, method,
						new HashMap&lt;String, Object&gt;(), true, false);

		// Add all request parameters to our action
		ActionContext invocationContext = proxy.getInvocation()
				.getInvocationContext();
		invocationContext.setParameters(new HashMap&lt;String, Object&gt;(request
				.getParameterMap()));

		// set the action context to the one used by the proxy
		ActionContext.setContext(invocationContext);

		// By default, have a clean session.
		proxy.getInvocation().getInvocationContext()
				.setSession(new HashMap&lt;String, Object&gt;());

		// Important - unit testing JSP's is just not very helpful.
		// do not execute the result after executing the action.
		proxy.setExecuteResult(false);

		// set the actions context to the one which the proxy is using
		ServletActionContext.setContext(proxy.getInvocation()
				.getInvocationContext());
		ServletActionContext.setRequest(request);
		ServletActionContext.setResponse(response);
		ServletActionContext.setServletContext(servletContext);
		return proxy;
	}

	/**
	 * Create a new dispatcher before every test.
	 */
	@Before
	public void initDispatcher() {
		// Dispatcher is the guy that actually handles all requests. Pass in
		// an empty. Map as the parameters but if you want to change stuff like
		// what config files to read, you need to specify them here. Here's how to
		// scan packages for actions. Thanks to Hardy Ferentschik, for action
		// packages ideas. (see Dispatcher's source code)
		dispatcher = new Dispatcher(servletContext, new HashMap&lt;String, String&gt;());
		dispatcher.init();
		Dispatcher.setInstance(dispatcher);
		request = new MockHttpServletRequest();
		response = new MockHttpServletResponse();
	}

	/**
	 * Initialize Spring context before the unit tests.
	 * 
	 * @throws Exception
	 *           When something bad happens.
	 */
	@BeforeClass
	public static void setUpContext() throws Exception {
		// Only initialize Spring context once - speeds up the tests a lot.
		if (applicationContext == null) {
			// this is the first time so initialize Spring context
			servletContext = new MockServletContext();
			servletContext.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,
					CONFIG_LOCATIONS);
			applicationContext = (new ContextLoader())
					.initWebApplicationContext(servletContext);

			// Struts JSP support servlet (for Freemarker)
			new JspSupportServlet().init(new MockServletConfig(servletContext));
		}
	}
}

 

So what’s going on – what do I need to know?

Rather simply but, I needed the unit-testing class to support the newer versions of the used libraries, so the above class is made to work with the following environment:

  • JUnit 4.10
  • Spring 3.1.0
  • Struts 2.3.1.2

Warning about Tiles and other results

There’s no tiles support in the above class, and executing the results have been disabled. If you want to enable executing the results you have to modify this line:

proxy.setExecuteResult(false);

You can hack it in there if you like but I couldn’t get it to work because of lack of time and because I really didn’t wanted to unit-test my JSP’s and tiles. Here’s the code you need to add to the setUpContext() method to make it initialize the tiles.

// Goes way in the top of the class as a static configuration.
private static final String TILES_CONFIG = "WEB-INF/tiles.xml";

// Goes into setUpContext() method in the end of the class file...
servletContext.addInitParameter(BasicTilesContainer.DEFINITIONS_CONFIG, TILES_CONFIG);
final StrutsTilesListener tilesListener = new StrutsTilesListener();
final ServletContextEvent event = new ServletContextEvent(servletContext);
tilesListener.contextInitialized(event);