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).
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... }
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
AnnotationsCreating 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.
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.
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.
// 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());
// 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"));
// 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"));
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.