Fork me on GitHub

The WOUnit framework contains a set of utilities for testing WebObjects applications using JUnit 4.7 or later capabilities.

WOUnit Basics

The WOUnit framework provides implementations of the MethodRule interface defined by JUnit. You don't have to know the internals of the MethodRule in order to write tests with WOUnit. It is enough to know that you need to annotate a public field in your test class with the @Rule (lines 8 and 9). Annotating the MockEditingContext field is sufficient to ensure that the editing context will be initialized before and disposed after each test case has been executed.

import static com.wounit.matchers.EOAssert.*;
import com.wounit.rules.MockEditingContext;
import com.wounit.annotations.UnderTest;
import org.junit.Rule;
import org.junit.Test;

public class FooTest {
	@Rule
	public MockEditingContext ec = new MockEditingContext("MyModel");
	
	@UnderTest
	private Foo foo;
	
	@Test
	public void cantSaveFooWithNullProperty() {
		foo.setProperty(null);
		
		confirm(foo, cannotBeSaved());
	}
}

From version 1.1 of WOUnit framework, the @UnderTest and @Dummy annotations were added to simplify the creation of enterprise objects. Fields annotated with one of those annotations are automatically populated with an inserted instance of the expected enterprise object type (lines 11 and 12).

Another feature of the WOUnit framework is a collection of matchers to facilitate the verification of conditions related to enterprise objects and editing contexts. If you static import the EOAssert methods then you'll be able to write more readable assertions (lines 1 and 18).

Unit Test with MockEditingContext

The MockEditingContext class provides means for fast in-memory testing of EOEnterpriseObjects. This class is useful for unit testing because it allows the creation of dummy objects that don't participate in the augmented transaction process. You can use it to validate the behavior of a unit in isolation.

You can pass an array of model names as the parameter of the MockEditingContext, and it will try to load the models automatically.

public class TestMyModel {
	@Rule
	public MockEditingContext ec = new MockEditingContext("MyModel");
	...
}

You can create real objects in the same way you are used to do in your application (line 3). Then you can create dummy objects to interact with them (line 5). The MockEditingContext does not track changes on dummy objects.

@Test
public void testingFooLogic() {
	Foo foo = Foo.createFoo(ec);
	
	Bar dummyBar = ec.createSavedObject(Bar.class);
	
	// Do something with foo using the dummy object...
}

You can also create and insert a dummy object. Very useful if you need to decorate or spy saved objects.

@Test
public void testingAnotherFooLogic() {
	Foo foo = Foo.createFoo(ec);
	
	// Decorate or spy a dummy object before inserting
	Bar dummyBar = spy(new Bar());
	
	ec.insertSavedObject(dummyBar);
	
	// Do something with foo using the dummy object...
}

Integration Test with TemporaryEditingContext

The TemporaryEditingContext class provides means to create temporary enterprise objects for unit testing. It is an editing context configured with the memory adaptor settings that is guaranteed to be reset when the test method finishes (whether it passes or fails).

This class is useful for integration testing because it mimics the internal behavior of the ERXEC. Every EOEnterpriseObject inserted in the TemporaryEditingContext behaves as a real EO. This kind of feature is useful to validate the behavior of a group of EOs.

public class MyModelTest {
	@Rule
	public TemporaryEditingContext ec = 
			new TemporaryEditingContext("MyModel");
	
	@Test
	public void testingMyModelLogic() throws Exception {
		Foo foo = Foo.createFoo(ec);
		
		Bar bar = Bar.createBar(ec);
		
		// Do something with foo and bar...
		
		// Verify a condition of foo and bar...
	}
}

@Dummy and @UnderTest Annotations

Creating the object under test and related dummy objects for each test case can be a very monotonous task.

public class FooTest {
	@Test
	public void testOneLogic() throws Exception {
		Foo foo = Foo.createFoo(ec);
		
		Bar dummyBar = ec.createSavedObject(Bar.class);
		
		// Do something with foo...
		
		// Verify a condition of foo or bar...
	}
	
	@Test
	public void testAnotherLogic() throws Exception {
		Foo foo = Foo.createFoo(ec);
		
		Bar dummyBar = ec.createSavedObject(Bar.class);
		
		// Do something else with foo...
		
		// Verify another condition of foo or bar...
	}
}

JUnit provides the @Before annotation to alleviate the repetitive task of creating test fixtures.
public class FooTest {
	private Foo foo;
	private Bar dummyBar;
	
	@Before
	public void setup() {
		foo = Foo.createFoo(ec);
		dummyBar = ec.createSavedObject(Bar.class);
	}
	
	@Test
	public void testOneLogic() throws Exception {
		// Do something with foo...
		
		// Verify a condition of foo or bar...
	}
	
	@Test
	public void testAnotherLogic() throws Exception {
		// Do something else with foo...
		
		// Verify another condition of foo or bar...
	}
}

However, even that solution requires a bit of boilerplate code. The @UnderTest and @Dummy annotations were developed to simplify the creation of enterprise objects.

public class FooTest {
	@UnderTest
	private Foo foo;
	
	@Dummy
	private Bar dummyBar;
	
	@Test
	public void testOneLogic() throws Exception {
		// Do something with foo...
		
		// Verify a condition of foo or bar...
	}
	
	@Test
	public void testAnotherLogic() throws Exception {
		// Do something else with foo...
		
		// Verify another condition of foo or bar...
	}
}

The @UnderTest annotation can be used to create real enterprise objects and insert them into the editing context rule. Usually, unit tests will have only one field annotated with @UnderTest and the need for more than one field annotated can be considered a test smell. This annotation can be used with both MockEditingContext or TemporaryEditingContext rules.

The @Dummy annotation can be used to create saved enterprise objects and insert them into the editing context rule. You can have as many fields annotated with @Dummy as required to test the object under test in isolation. This annotation only works with the MockEditingContext rule.

Creating an Array of EOs Using Annotations

Since WOUnit 1.2, the @UnderTest and @Dummy annotations can be used to create an array of EOs.

public class ArrayTest {
	@UnderTest(size = 2)
	private NSArray<Foo> arrayOfFoos;
	
	@Dummy(size = 5)
	private NSArray<Bar> arrayOfDummyBars;
}

The size parameter defines the number of elements to create and add into the NSArray. If the size is omitted, then the array will be created with only one element.

For advanced testing, the Mockito's @Spy annotation can be used in the companion of the @UnderTest and @Dummy annotations. If the @Spy annotation is present, then the elements in the NSArray will be spied. Learn more about the Mockito integration in the Advanced Testing with WOUnit + Mockito section.

Assertion with EOAssert

The EOAssert class is the entry point for writing assertions to WebObjects related unit tests. This class provides static methods to check conditions against enterprise objects and editing contexts.

Enterprise Object State

// Checks whether the eo has been saved and has no pending changes
confirm(eo, hasBeenSaved());

// Checks whether the eo has not been saved
confirm(eo, hasNotBeenSaved());

// Checks whether the eo has been deleted
confirm(eo, hasBeenDeleted());

// Checks whether the eo has not been deleted
confirm(eo, hasNotBeenDeleted());

Enterprise Object Validity

// Checks whether the eo can be saved
confirm(eo, canBeSaved());

// Checks whether the eo cannot be saved
confirm(eo, cannotBeSaved());

// Safer check whether eo cannot be saved
confirm(eo, cannotBeSavedBecause("The foo property can't be null"));

// Checks whether the eo can be deleted
confirm(eo, canBeDeleted());

// Checks whether the eo cannot be deleted
confirm(eo, cannotBeDeleted());

// Safer check whether eo cannot be deleted
confirm(eo, cannotBeDeletedBecause("It is a required object"));

Editing Context Save Changes

// Checks whether the editing context save changes successfully
confirm(editingContext, saveChanges());

// Checks whether the editing context does not save changes
confirm(editingContext, doNotSaveChanges());

// Safer check whether the editing context does not save changes
confirm(editingContext, doNotSaveChangesBecause(
		"The bar property of Foo cannot be null"));

Advanced Testing with WOUnit + Mockito

Mockito is a powerful mocking and test spy framework. Test spy is a useful technique that allows to verify behavior and stub methods. Since WOUnit 1.2, fields annotated with @UnderTest and @Dummy can be spied using the Mockito's @Spy annotation.

@RunWith(MockitoJUnitRunner.class)
public class SpyTest {
	@Spy @UnderTest
	private Foo foo;
	
	@Test
	public void testOneLogic() throws Exception {
		doReturn("text").when(foo).stringMethod();
		...
		verify(foo).aMethod();
	}
}

The test case must be run with the MockitoJUnitRunner (line 1) to spy objects properly. The @Spy annotation (line 3) can be defined in the companion of WOUnit annotations. The resulting object is a valid EO that can have methods stubbed (line 8) and can be verified (line 10).

So far, so good. Mockito can stub methods and verify the behavior, but what kind of real problem can it solve?

Suppose a test for a piece of code that needs to fetch the sum of values in the database. Wonder helper classes allow the construction of a simple SQL query like "SELECT SUM(value) FROM table". Running a query like that hits the database, which is undesirable during the execution of a unit test. Mocking the Wonder helper classes is not an option in this case. How can one test some logic that depends on this kind of behavior?

The following code demonstrates a solution for this problem.

@RunWith(MockitoJUnitRunner.class)
public class StubSumTest {
	@Spy @UnderTest
	private Foo foo;
	
	@Test
	public void testLogicRequiresSum() throws Exception {
		// Stub the invocation of fetchSumOfValues to always return 10
		doReturn(10).when(foo).fetchSumOfValues();
		
		// Execute the real logic that requires the sum of value
		foo.realMethod();
		...
	}
}

The code that executes the query can be isolated in a package private method and, combined with the Mockito stub mechanism, can solve that problem (line 9). When the fetchSumOfValues method is called during the realMethod execution, it will return 10 without reaching the database.