Unit Testing
Core Commerce extensively uses the JUnit testing framework. This test coverage provides a quick and effective way to detect bugs that may have been introduced while making modifications. When customizing, it is good practice to write JUnit tests for new or modified code and run all JUnit tests prior to committing changes. Core JUnit tests find Spring configuration errors at build time, so you need not wait until the server starts up to find bugs such as duplicate bean names or XML format errors.
Running JUnit Tests
JUnit tests can be run via Maven or from within the Eclipse development environment.
Running Tests with Maven
JUnit tests in Elastic Path Commerce can be executed by running the following Maven tasks from the root of the project:
To run all JUnit tests:
mvn install
To run all JUnit tests and JUnit integration tests:
mvn install -Pwith-itests
To run an individual JUnit test:
mvn install -Dtest=<test_class_name>
To run an individual JUnit integration test:
mvn install -Dit.test=<test_class_name>
Maven will report the status of the tests in the command line. For example:
0:21:24 : -------------------------------------------------------
10:21:24 : T E S T S
10:21:24 : -------------------------------------------------------
10:21:25 : Running com.elasticpath.domain.store.impl.WarehouseImplTest
10:21:25 : Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.061 sec
10:21:25 :
10:21:25 : Results :
10:21:25 :
10:21:25 : Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
Maven creates an XML file during this process for each test that will include additional details about failing tests. The test output files can be found in <artifact_Id>\target\surefire-reports
and the file names are composed of the package name and file name of the test case.
Running Tests with Eclipse
JUnit tests can also be run from within Eclipse. To run an individual test case, right-click on the test file in the Package Explorer and select run as... -> JUnit Test. You can also run all tests in an entire project by right-clicking on the project in the Package Explorer and selecting Run As... -> Junit Test.
Setting memory options in Eclipse
The default memory for launching applications in Eclipse is insufficient for executing all unit tests in the core project. You can increase the default memory settings by navigating to Window -> preferences -> Java -> Installed JREs -> Select your JRE -> Edit and set your memory settings in the Default VM Arguments input box. The recommended setting for JRE (Java Runtime Environment) is -Xmx512m
.
Creating Unit Tests
Adding a New Unit Tests
The JUnit test case for a class is typically the name of the class with Test appended at the end. Test cases should be in the same package as the class they test so that they have protected access to the members in the class. To avoid cluttering the production code with unit test classes, place unit tests in a test directory structure that mirrors the main source folder. For example, the Cortex JUnit tests are in the same package structure as the java code they are testing, but they are in the "test" folder.
Source Code Structure
Within the test case, a constructor is not required unless there is initialization that must be performed only once for the entire test case. A main method that runs the text UI or Swing UI is also not required as it is not used by the Eclipse JUnit runner the or Maven JUnit task.
Test cases in Elastic Path typically use the setup() method to instantiate the object tested by each test in the test case. The setup() method is also frequently used to configure any Jmock mock objects that are not specific to any one test.
JMock
The JMock framework facilitates testing of a single class in isolation. JMock eases the creation of mock objects, which are test object instances that a class under test can reference. By using mocks, you can control the output that a class under test receives from objects it depends on so that you can test specific situations. Furthermore, mock objects can be given expectations of which methods will be invoked on it by the class under test. These expectations can optionally specify the parameters that are passed into the method as well as the number of times the method is invoked.
JMock is used in conjunction with JUnit, so you create your mock objects inside a JUnit test case. If the expectations for a mock object are not met, the JUnit test case fails.
Using JMock
The following code shows a typical JMock2 mock object usage pattern. In this example, we test the Product
class and we use a mock ProductType
object to test the interaction of Product
class with ProductType
.
@Before
public void setUp() {
context = new JUnit4Mockery();
}
@test
public void testProductInteractionWithProductType(){
Product productImpl = new ProductImpl();
//Create a mock object to mock a product type
final ProductType mockProductType = context.mock(ProductType.class);
//Pass the mock object to the product class
productImpl.setProductType(mockProductType);
//Create the object that the mock object returns
AttributeGroup expectedAttributeGroup = new AttributeGroupImpl();
//Specify that the getProductAttributeGroup() method of product type must
//be called one time and that it will return to the product the attribute
//group we wish to test the product with
context.checking(new Expectations() {
{
oneOf(mockProductType).getProductAttributeGroup();
will(returnValue(expectedAttributeGroup));
}
});
//Invoke the method being tested, which will interact with the mock object
AttributeGroup returnedAttributeGroup = productImpl.getAttributeGroup();
//Check the results of the operation here
assertAttributeGroupsAreEqual(expectedAttributeGroup, returnedAttributeGroup);
}
Primitive types must be specified exactly in Jmock constraints
Jmock is very strict regarding types in constraints. For example, take the get()
method signature in the PersistencEngine
class shown below:
Persistence get(final Class persistenceClass, final long uidPk) throws EpPersistenceException;
This method cannot be mocked as shown below because the "1" that is passed to equals is an int instead of a long.
allowing(mockPersistenceEngine).get(with(equal(Brand.class)), with(equal(1)));
will(returnValue(brand1));
Instead, the method must be mocked with the type explicitly stated as shown below.
allowing(mockPersistenceEngine).get(with(equal(Brand.class)), with(equal(1L)));
will(returnValue(brand1));