Liferay Testing Infrastructure

Intro #

This article summarizes some of the most relevant parts of the testing infrastructure that is used to test Liferay. This infrastructure provides a common framework which allow us to write new tests in the easiest way possible. Our main goal with all this stuff is to increase our code quality and make the portal as robust as possible

This article will describe the different kind of tests we have at Liferay, how they work and how new tests can be written. We will be talking about many different things:

  • Unit tests:
    • Goals of unit testing
    • Mocking libraries: Mockito + Powermock
  • Integration tests
    • Goals of integration testing
    • Testing out of the container
    • Liferay testing listeners support
    • Making our tests transactional
  • How can I run the tests?

Liferay QA also uses Selenium to write lots of functional tests, however that is outside of the scope of this article.

Unit testing #

Using the unit tests we are testing our components in an independent manner so we can "ensure" that our piece of software is working correctly in an isolated way. Let´s see some examples of existing unit tests in the portal:

Unit test with no dependencies #

A good example of this type of test is the class AntlrCreoleParserTests. Writing this kind of unit tests is very easy because the piece of software we are testing, the Creole10Parser, has no external dependencies so we don´t need to mock (we will be speaking about mocks in a few moments) them. As we have said before, this is very easy, we just need to create a new class, and include our testing logic. Here is a small example

	@Test
	public void testParseHeadingBlocksMultiple() {
		WikiPageNode wikiPageNode = getWikiPageNode("heading-10.creole");

		Assert.assertEquals(3, wikiPageNode.getChildASTNodesCount());
	}	

Note we are annotating the method with the JUnit @Test annotation. You can use the BaseTestCase class as the parent of your test class, it will depend on your needs.

Unit test with external dependencies #

All the tests are not as easy as the previous one because a piece of software may have a dependency on an external item in order to achieve its work. At this point, we want to test the behaviour of our code in an isolated way, assuming the code of our dependencies are working perfectly.

We use the mocking approach in order to achieve this goal through the PowerMock library plus its Mockito extesions Powermock. We are not covering how this libraries work but the use of them we are using at Liferay. Reference Card about Mockito

As an example, let´s analyze the main parts of the StripFilter class in order to explain how the tests work:

We apply the following two annotations to the test class definition:

	
	@PrepareForTest({CacheKeyGeneratorUtil.class, PropsUtil.class})
	@RunWith(PowerMockRunner.class)

The @RunWith annotation tells Junit to use the PowerMockRunner instead of the default runner in order to allow the Powermock library to do its work. The @PrepareForTest annotation prepares both classes for being instrumented because the StripFilter has some external dependencies expressed as calls to static methods that we need to mock.

The next step is to write a new method and put our testing logic on it. Here is the sample structure of such a test that exercises the processCSS() method:

	@Test
	public void testProcessCSS() throws Exception {
	
		// prepare data test

		// call the method we are testing 

		stripFilter.processCSS(null, null, charBuffer, stringWriter);

		// do the required asserts in order to verify everything is fine
	}

When writing this type of test with mocks we often need to look into the code of the tested method. In this case we find out that the StripFilter class makes some calls to the method getCacheKeyGenerator on the class CacheKeyGeneratorUtil. As said before, we want to test the StripFilter in isolation, so we need to mock that invocation:

	mockStatic(CacheKeyGeneratorUtil.class);

	when(
		CacheKeyGeneratorUtil.getCacheKeyGenerator(
			StripFilter.class.getName())
	).thenReturn(
		new HashCodeCacheKeyGenerator()
	);

We are describing how Powermock should behave when the method call CacheKeyGeneratorUtil.getCacheKeyGenerator() is invoked. Using the previous approach we are not invoking the real code but "mocking" it so we can assume everything we depend on is working fine because we are describing how it should work.

Once our testing logic has been executed we need to verify that everything has worked as expected, by doing some asserts on the data. In addition, we can include an extra assert like this:

	verifyStatic();	

which verifies if our "mocks" have worked as we have defined on the beginning of the test.

In some other cases (actually this should be the most common case), the methods that we are testing don't invoke static methods but rather method calls in object instances. Let's see an example of how to mock those object instances. In particular we will use the class TikaRawMetadataProcessor as an example. Here is a copy of the most relevant code of that class:

	public class TikaRawMetadataProcessor extends XugglerRawMetadataProcessor {

		. . .

		protected Metadata extractMetadata(
				InputStream inputStream, Metadata metadata)
			throws IOException {
		
			. . .

			_parser.parse(inputStream, contentHandler, metadata, parserContext);
			
			. . .

			return metadata;		
		}

		private Parser _parser;
	}
	

As we can see, it has a dependency on an instance of class Parser. In order to test the methods that depend on that instance we need to write some code and use some annotations that will replace that instance with a mock. Here is an example of how we would do that in our test class:

	
	class TikaRawMetadataProcessorTest {
		
		. . .

		@Mock
		private Parser _parser;

		@InjectMocks
		private TikaRawMetadataProcessor _tikaRawMetadataProcessor =
			new TikaRawMetadataProcessor();	
	}	

The previous snippet show us how we need to configure our test:

  1. The first step is to tell the library that we are creating a mock in the _parser field. By applying @Mock, Mockito will automatically pupulate the field with a mock implementation of Parser.
  2. The second step requires injecting the previous mock into the metadata processor. By applying @InjectMocks, mockito will invoke the setters of TikaRawMetadataProcessor for each field in TikaRawMetadataProcessorTest that has the @Mock annotation.

At this point we are ready to mock all the required behaviour in order to validate our software component is working fine.

Since all of the above where just partial code excerpts you may want to take a look at the full test classes mentioned in this section. Here they are:

There are many more examples of unit tests around the source code of Liferay Portal. Search for the folder test/unit in the source folder of the portal.

Integration testing #

The previous section describes how we can test certain classes or methods in isolation so we can ensure that the code of an specific class or method is working fine, in isolation of all its dependencies. But it is also very important to verify that our piece of software behaves as expected when interacting with its real dependencies: this is the goal of integration testing.

At the time of this writing, we are using a "testing out of the container approach" so we don't need to deploy the Liferay platform on an existing application server but we are starting the Spring application context. We know we can not write certain kind of tests witouth running inside an app server but our testing infrastructure is growing up and, hopefully, this will be solved in the near future; stay tuned.

Following the same pattern that the previous version, let's see some examples of how we can write integration tests:

Starting the Spring application context #

All the integration tests require the Spring application context is fully up and running; so we need to start it before our test code gets executed. This is a very easy task, you only need to use a concrete JUnit runner: LiferayIntegrationJUnitTestRunner.

Let's see how we can write an integration test for the XhtmlTranslationVisitor:

	@RunWith(LiferayIntegrationJUnitTestRunner.class)
	public class TranslationToXHTMLTest extends AbstractWikiParserTests {
	
		. . .

		private XhtmlTranslationVisitor _xhtmlTranslationVisitor =
			new XhtmlTranslationVisitor();

	}

As you can see, this is very easy, we only need to annotate our test class with the @RunWith annotation and use the runner we have spoken about just a few lines ago. In this case, the _xhtmlTranslationVisitor has an external dependency on the HTMLUtil (which is injected through Spring).

The listener framework #

The previous runner, in addition to configure and run the Spring application context, allows us to define test listeners which will get executed at different points of the execution process. These points are:

  1. Before Test: Execute code before each test is executed
  2. After Test: Execute code after each test is executed
  3. Before Class: Execute code before all the tests in a class are started
  4. After Class: Execute code after all the tests in a class are finished

The previous "execution points" are defined in the interface ExecutionTestListener.

Once I have created a new class implementing the previous interface, how can I use it on my tests? Again, we are using an annotation, ExecutionTestListeners, in order to achieve that. Mark your class with the previous annotation and specify the listeners you want to execute:

	
	@ExecutionTestListeners(listeners = {FirstExecutionTestListener.class, SecondExecutionTestListener.class})
	@RunWith(LiferayIntegrationJUnitTestRunner.class)
	public class MyIntegrationTest {

		@Test
		public void testFoo() {
			
		}
	}

At the time of this writing, the set of available listeners are the following:

Execution test listeners hierarchy

The following lines briefly describe the functionality of each of the previous listeners:

  • AbstractExecutionTestListener: Base class for all the listeners in the platform
  • EnvironmentExecutionTestListener: Configure all the permissions and services before all the tests are executed. Destroy the services once all the tests have been executed.
  • TransactionalExecutionTestListener: Open a transaction before each test is executed and make a rollback once the test execution has finished. The database remains at the same status that it was at the begining of the test. It is required to annotate your class or your method with @Transactional
  • PersistenceExecutionTestListener: Achieves the same work than the previous listener but using a different approach. It is used only on generated code (by ServiceBuilder) so you don't need to use it.
  • TransactionalCallbackAwareExecutionTestListener: Some set of services raise some events that should be run after the commit phase. This listener tries to emulate this behaviour as much as possible. Some additional work will be required on this. This is only used in some very specific scenarios.
  • WebDAVEnviornmentConfigTestListener: Prepares the environment for the WebDav tests. You will not need to use it unless you are testing some of the WebDav components.
  • MainServletExecutionTestListener: Set up and configure the MainServlet before all the tests are executed. IMPORTANT: at the time of this writing an stracktrace related to the autodeployers is shown when using this listener. Just ignore it; this is related to the fact that we are not running inside any container.

Writing integration tests is generally easier than its unit counterparts because we don't need to simulate the original behaviour of the dependencies but using the real ones instead. Search for the folder test/integration in the portal source code and you will find lot of integration tests.

Initial Configuration #

Liferay's tests needs some configuration to be executed.

build.USERNAME.properties #

You can set up some utility configs when building the portal. An example of this file could be:

javac.compiler=modern
shell.executable=/bin/bash
jsp.precompile=on
junit.test.excludes=**/TransactionInterceptorTest.class
source.formatter.excludes=/portal-ext.properties

portal-test-ext.properties #

You can set up your database configuration, or anothert portal properties in this file, that is only read by the test. An example of this file could be:

jdbc.default.driverClassName=com.mysql.jdbc.Driver
jdbc.default.username=username
jdbc.default.password=password
jdbc.default.url=jdbc:mysql://localhost/lportal_testing?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false
setup.wizard.enabled=false
liferay.home=/home/user/servers/master
junit.test.excludes=**/TransactionInterceptorTest.class
ehcache.portal.cache.manager.jmx.enabled=false
index.with.thread=false
index.on.startup=true
value.object.listener.com.liferay.portal.model.LayoutSet=
dl.file.entry.processors.trigger.synchronously=true
journal.transformer.regex.pattern.0=http://beta.sample.com
journal.transformer.regex.replacement.0=http://production.sample.com

Running the tests #

Liferay's tests are executed through different Ant tasks.

Single class #

If you want to run a single test class you can use the

test-class
task:

ant test-class -Dtest.class=NameOfTheClass
ant test-class -Dtest.class=NameOfTheClass -Djunit.debug=true (run the tests in debug mode: you can find the debug options in the build.properties file)

You don't need to include the qualified name of the class, only its name.

Individual methods #

If you want to run specific individual methods of the test class you can pass a comma delimited list of method names with the

test-method
task:

ant test-method -Dtest.class=NameOfTheClass -Dtest.methods=someMethod,someOtherMethod

Batch several classes - 6.2 #

If you want to run a subset of several classes at once you can now pass a 'ant' style regex path pattern using the test.class property to the

test-class
task:

ant test-class -Dtest.class=some/path/*Test

The above will execute all classes which match the path

.*/some/path/.*Test\.class

Individual packages - 6.2 #

If you want to run all classes under a package at once you can now pass the package name using the package property to the

test-package
task:

ant test-package -Dtest.package=com.liferay.portal.dao.orm

The above will execute all classes under the package 

com/liferay/portal/dao/orm/**/*.class

IMPORTANT: You can run the tests for an specific subfolders of the Liferay source code moving to portal-impl, portal-service, . . . or you can run all tests of a kind from portal root source code folder. 

You can type in the selected source root folder

	
	ant test-integration

or

	
	ant test-unit

in order to run all the integration tests or the unit tests, respectively.

1 Adjunto
26072 Accesos
Promedio (4 Votos)
La valoración media es de 5.0 estrellas de 5.
Comentarios
Respuestas anidadas Autor Fecha
Good article. Sohui Gu 11 de marzo de 2014 8:44
Hi, Is this possible to work with Maven for... kian ping low 15 de abril de 2014 21:49
I am able to run the basic/simple test from... Haris Ahmed 8 de enero de 2015 6:15

Publicado el día 11/03/14 8:44.
Hi,
Is this possible to work with Maven for Integration Test?

As for unit test, I'm using JMockito and PowerMock. but for Integration Test, i am having issue trying to initialize the liferay context if i want to make a call to liferay services like UserLocalServiceUtil, to get the user details.

Will you able to to advice?
Publicado el día 15/04/14 21:49.
I am able to run the basic/simple test from Ext-Plugins using "ant test".

Question is How to run Unit and Integration tests from Ext-Plugin, if test is dependent on the Liferay Portal Services?
Publicado el día 8/01/15 6:15.