Archive

Posts Tagged ‘Testing’

Testing Documentum Customizations without Documentum

January 5th, 2012 1 comment

Usually when developing customizations for Documentum I test the customization by running it against a Documentum repository. This is easy to do but it has a number of drawbacks. First of all this requires the availability of a Documentum server and a connection to a repository.  Second, this repository must also be setup in such a way that your code can be tested. This could mean that your code has to be installed in the repository before you are able to test it. And third, this requires that most of your code is in a testable state, i.e. all components can be compiled and run without apparent errors.

At times it is desirable to be able to test your code without actually running it against a Documentum content server. This may simply be the case if you do not have access to a Documentum content server to test against, or maybe your code is part of a continuous build process where having access to a content server is not desirable.

Writing unit tests that do not require actually running against a Documentum server means that we have to dive into the world of mock objects. Using mock objects we can simulate the desired behavior of objects and interfaces without using the “real” object. If we can use mock objects to simulate the behavior of Documentum DFC objects we should not need an actual Documentum server to run our unit tests. The big question of course is: can it be done?

First of all you need to use a framework for generating mock objects. I started out using EasyMock (www.easymock.org) but soon ran into its limitations in that it cannot mock static classes or private methods. With EasyMock you can probably get quite far but this will require that you develop your code around your unit tests.

If you are really serious about writing your Documentum unit tests you will need a framework like PowerMock (www.powermock.org). PowerMock can be used as an extension of EasyMock and gives you – amongst other things – the ability to mock static classes, mock private methods, create a partial mock of an object or even set a private variable of a class to a specific value.  With PowerMock you can write your test units around your code instead of the other way around.

Unit testing a DfSingleDocbaseModule

Now let’s get to work. Assume you have a custom object type called “my_document”, subtype of “dm_document”. This object type has a custom String attribute called “special_value”. The “my_document” object type has its own TBO that is accessed through the “IMyDocumentTbo” interface.  The “IMyDocumentTbo” interface contains the “getSpecialValue” method which retrieves the value of the “special_value” attribute.

We also have a workflow with a BOF Module activity that retrieves the “special_value” value of an object of type “my_document”. The BOF Module activity uses a custom module consisting of the “IMyModule” interface and the “MyModuleImpl” implementation class that extends the “DfSingleDocbaseModule” class.

The “IMyModule” interface looks like this:

public interface IMyModule {

String getObjectSpecialValue(final String objectId) throws DfException;

}

The “MyModuleImpl” class typically looks something like this:

 public class MyModuleImpl extends DfSingleDocbaseModule implements IMyModule {

    public final String getObjectSpecialValue(final String objectId)
throws DfException {

		String specialValue = "";
		IDfSession session = null;
		IDfSessionManager mgr = null;
		try {
			mgr = this.getSessionManager();
			session = mgr.getSession(this.getDocbaseName());
			IMyDocumentTbo document = (IMyDocumentTbo) session.
				getObject(new DfId(objectId));
			specialValue = document.getSpecialValue();
		} catch (DfException e) {
			DfLogger.error(this, "getObjectSpecialValue::Error: ", null, e);
			throw e;
		} finally {
			if (session != null) {
				mgr.release(session);
			}
		}
		DfLogger.debug(this, "getObjectSpecialValue::specialValue=[{0}]",
			new String[] {specialValue}, null);
		return specialValue;
	}
}
 

Writing a unit test for the “MyModuleImpl” class has a number of challenges. The static class DfLogger is used for logging purposes. Since we do not really want to do any logging we need to mock this class. PowerMock supports this with the “PowerMock.mockStatic()” method. Furthermore the private methods “getSessionManager()” and “getDocbaseName()” of the “DfSingleDocbaseModule” class are used to get access to Documentum. Because we do not really want to access Documentum we need to mock the behavior of these methods. Fortunately PowerMock also supports the concept of a partial mock, which means that some methods of a class can be mocked while others are executed normally.
A PowerMock unit test typically consists of 4 parts:

  1. The definition of the objects to mock.
  2. The specification of expected behavior of the unit to test.
  3. Replaying the test.
  4. Verifying the result.

For the test unit we need to mock a number of objects:

  • the Documentum session manager
  • the Documentum session
  • the custom Documentum object (my_document)
  • the DfLogger

We also need to partially mock the module itself to be able to define the behavior of the “getSessionManager()” and “getDocbaseName()” methods. The list of methods to mock can be supplied to the “PowerMock.createPartialMock()” method as a String array.

So the definition of the objects to mock looks like this:

final String[] mockMethods = new String[] {"getSessionManager", "getDocbaseName"};
final IMyModule moduleMock = PowerMock.createPartialMock(MyModuleImpl.class, 	mockMethods);
final IDfSessionManager mgrMock = PowerMock.createMock(IDfSessionManager.class);
final IDfSession sessionMock = PowerMock.createMock(IDfSession.class);
final IMyDocumentTbo documentMock = PowerMock.createMock(IMyDocumentTbo.class);
PowerMock.mockStatic(DfLogger.class);

Now we have to define the expected behavior of the module method we want to test. In this example there are 3 ways in which behavior is expected:
1. We expect some public methods to be executed that return a value, i.e. an object of some kind. This behavior is expected using the regular “expect” method from EasyMock.
2. We expect some private methods to be executed that return a value, i.e. an object of some kind. This behavior is expected using the “PowerMock.expectPrivate()”method from PowerMock.
3. We expect some public methods to be executed that do not return any value (void methods). These methods can just be mentioned without expecting any return value.
The trick is that when a return value is expected we can tell our test unit to return one of the mocked objects instead of a real object. With this mocked object we can further specify expected behavior.

For “MyModuleImpl” the expected behavior looks like this:

PowerMock.expectPrivate(moduleMock, "getSessionManager").andReturn(mgrMock);
PowerMock.expectPrivate(moduleMock, "getDocbaseName").andReturn(REPOSITORY);

expect(mgrMock.getSession(REPOSITORY)).andReturn(sessionMock);
expect(sessionMock.getObject(anyObject(DfId.class))).andReturn(documentMock);
expect(documentMock.getSpecialValue()).andReturn(SPECIAL_VALUE_VALUE);

DfLogger.debug(anyObject(Object.class), anyObject(String.class), 	anyObject(String[].class), anyObject(Exception.class));

The first 2 lines specify the expected behavior of the mocked private methods of the module, returning the session manager mock and the name of the repository. The following 3 lines specify the expected behavior of some public methods on the session manager, the session and the document mock objects. The final 2 lines specify expected behavior of methods that do not return any value.
To replay and verify the expected behavior we need to tell PowerMock which mocks to replay and which to verify. Fortunately PowerMock contains “replayAll” and “verifyAll” methods so that we do not actually need to specify the separate mocks. Between replaying and verifying we need to perform the action we want to test en specify what behavior is correct or not.

For “MyModuleImpl” replaying and verifying looks like this:

String specialValue;
try {
	PowerMock.replayAll();
	specialValue = moduleMock.getObjectSpecialValue("anyId");
	assertEquals(specialValue, SPECIAL_VALUE_VALUE);
	} catch (DfException e) {
		fail("Unexpexted DfException");
	}
PowerMock.verifyAll();

Finally to correctly execute the test PowerMock uses a number of annotations that need to be specified for the unit test class. These annotations are:
• @RunWith(PowerMockRunner.class)specifies that the unit test is run using PowerMock
• @PrepareForTest({…}) specifies an array of classes to use during the test. Unfortunately the PowerMock documentation is not quite clear on which classes to include. My advice is: include all static classes you are testing and all custom classes.

For the “MyModuleImpl” unit test the following setup works:

@RunWith(PowerMockRunner.class)
@PrepareForTest( {MyModuleImpl.class, IMyDocumentTbo.class, DfLogger.class})

Putting it all together the entire unit test looks like this:

@RunWith(PowerMockRunner.class)
@PrepareForTest( {MyModuleImpl.class, IMyDocumentTbo.class, DfLogger.class})
public class TestModuleWithMock extends TestCase {

	private static final String REPOSITORY = "repository";
	private static final String SPECIAL_VALUE_VALUE = "This is your special value";

	@Test
	public final void testModule_getSpecialValue() throws Exception {
		final String[] mockMethods = new String[]
			{"getSessionManager", "getDocbaseName"};
		final IMyModule moduleMock = PowerMock.createPartialMock(MyModuleImpl.class,
			mockMethods);
		final IDfSessionManager mgrMock = PowerMock.createMock(IDfSessionManager.class);
		final IDfSession sessionMock = PowerMock.createMock(IDfSession.class);
		final IMyDocumentTbo documentMock = PowerMock.createMock(IMyDocumentTbo.class);
		PowerMock.mockStatic(DfLogger.class);

		PowerMock.expectPrivate(moduleMock, "getSessionManager").andReturn(mgrMock);
		PowerMock.expectPrivate(moduleMock, "getDocbaseName").andReturn(REPOSITORY);

		expect(mgrMock.getSession(REPOSITORY)).andReturn(sessionMock);
		expect(sessionMock.getObject(anyObject(DfId.class))).andReturn(documentMock);
		expect(documentMock.getSpecialValue()).andReturn(SPECIAL_VALUE_VALUE);

		DfLogger.debug(anyObject(Object.class), anyObject(String.class),
			anyObject(String[].class), anyObject(Exception.class));
		mgrMock.release(sessionMock);
		String specialValue;
		try {
			PowerMock.replayAll();
			specialValue = moduleMock.getObjectSpecialValue("anyId");
			assertEquals(specialValue, SPECIAL_VALUE_VALUE);
		} catch (DfException e) {
			fail("Unexpexted DfException");
		}
		PowerMock.verifyAll();
	}
}

Once you have this test running correctly you can extend the test by including exception handling. For instance you could change the behavior of the “sessionMock.getObject()” to throw a DfException and verify that this is handled correctly.

To wrap it up
Using PowerMock you can get a long way into writing unit tests that do not require access to Documentum. Still there will be challenges ahead. Unit testing TBO’s for instance seems to pose some problems of its own. But if you’re serious about unit testing this way you may achieve a fair amount of code coverage.

But then, is it worth it? If you start writing this kind of unit test, you have to realize that in some cases you will have to put in at least as much effort into writing the unit test as you have done writing the unit itself. Be sure to mention this to your project manager in advance.

Jeroen Teeling
ECM Consultant