Plugin Unit testing in Liferay 6.2
In the past I tried to find out how to unit test my plugins in liferay with no success. To be honest, I never tried much, and I am also a rookie in unit testing. I always wanted to learn to use test driven development but the pressure at work didn't helped me to change to that direction. Perhaps now I will succeed.
The existing documentation on how to unit test plugins in liferay 6.2 is as good as none. There is some for 6.1, but it is not more valid in 6.2. So, if the things I wrote could be done in a better way (or are wrong), please notify me, I will appreciate any help.
I tried to write the document in a simple way, and to break it in steps, so that every part of the configuration needed for every case is easy to be understood.
So, I will break the article in five parts:
- Infrastructure: the folder structure should follow specific names, so that the sdk ant tool can find everything in tis place
- Unit Testing: a simple unit test
- Unit Testing with mockups: a more advanced unit test using a mockup library
- Integration Testing with Liferay core Services
- Integration Testing with custom Plugin Services *
(*) This section is not ready yet. I can't find a way to load the custom services in the spring container, but I am working on that.
The ant tool in the liferay SDK searches for test classes in specific directories, so I decided to follow this structure also in the article, because this way it makes more sense.
Infrastructure
In your eclipse project (I use eclipse as my IDE) create a new folder with the name "test" and under it create the following tree:
+-- docroot
|
+-- test
|
+-- unit
| |
| +-- src
|
+-- integration
|
+-- src
Add the two "src" folders in your build path.
In unit/src you will place your unit test classes and in integration/src the integration test classes.
Having this infrastructure allows us to execute our unit and integration tests from the liferay sdk ant tool.
We have these options:
- ant test : runs all the unit and integrations tests in the subfolders
- ant test-unit : runs all unit tests
- ant test-integration: runs all integration tests
If you like to see what those targets do, you can view the code in the file "build-common-plugin.xml", which is located in the root folder of the liferay SDK. You have to run those targets from inside your project directory, using the projects build.xml.
After running one of the above commands for the first time, two new directories will be created, like this:
+-- docroot
|
+-- test
|
+-- test-classes
|
+-- test-results
|
+-- (liferay - existence under condition)
The folder "test-classe" contains your compiled classes and the folder "test-results" contains the test results in an xml form, so that those can be used in eclipse (try to double click on them) or in a continuous integration server (like jerkins).
If you use (that will be mention later in the article) the hypersonic database, you will see also a "liferay" directory on the same level, that contains all the data of this database.
Unit Testing
Let us now create a simple unit test for our project. All we need is to create a class in the unit/src folder, for example the "UnitTest.java" with the following code:
import static org.junit.Assert.*; import org.junit.After; import org.junit.Before; import org.junit.Test; public class UnitTest { @Before public void setUp() throws Exception { } @After public void tearDown() throws Exception { } @Test public void test() { fail("Not yet implemented"); } }
This code will always fail, but you can insert your code there and do your testing. After running the "ant unit" task:
test-cmd:
[junit] Running UnitTest
[junit] Testsuite: UnitTest
[junit] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.565 sec
[junit] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.565 sec
[junit] Testcase: test(UnitTest):FAILED
[junit] Not yet implemented
[junit] junit.framework.AssertionFailedError: Not yet implemented
[junit] at UnitTest.test(UnitTest.java:24)
[junit] Test UnitTest FAILED
Unit Testing with mockups
If you want to create some useful unit tests, you will have to mock some classes. The liferay SDK has already included the libraries of PowerMock and Mockito when run nit the tests with the ant tool.
So, we need to add those libraries also in our eclipse project. The best way to do this was to define a linked folder with the path "../../lib". In the linked folder you will find all the jars from the SDK. Select "mockito-all.jar" and all with the pattern "powermock-*.jar" and add those to your build path.
Now we are ready to use mocking in our unit tests. Try the following changes in the "UnitTest.java" file:
import static org.junit.Assert.assertEquals; import static org.powermock.api.mockito.PowerMockito.when; import com.liferay.portal.kernel.exception.SystemException; import com.liferay.portal.service.UserLocalServiceUtil; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) @PrepareForTest(UserLocalServiceUtil.class) public class UnitTest { @Before public void setUp() throws Exception { } @After public void tearDown() throws Exception { } @Test public void test() throws SystemException { PowerMockito.mockStatic(UserLocalServiceUtil.class); when(UserLocalServiceUtil.getUsersCount()).thenReturn(5); assertEquals(5, UserLocalServiceUtil.getUsersCount()); } }
After run nit "ant unit" you should get:
[junit] Running UnitTest
[junit] Testsuite: UnitTest
[junit] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.736 sec
[junit] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.736 sec
Integration Testing with Liferay core Services
If you want to use a core liferay service, for example the class UserLocalServiceUtil, you will need to initiate the portal spring container. You can do this by using the "InitUtil.initWithSpring()" method. But there a catch here: the class InitUtil is not part of the SDK. You can find it in the portal-impl.jar. Running the tests from the ant tool (which is part of the SDK) includes this library in the build path. But eclipse doesn't know its existence. But there is a nice solution to this: just add download the source code of the portal, create a eclipse project from that and then create a project reference from your project to that source code. You will gain two things. First, you will have access to the InitUtil class, and to all the portal source, which is (till now) the best documentation you will find for liferay.
With the reference to the portal source you can now begin writing your tests. Add the spring initialisation code in the setUp method:
@Before public void setUp() throws Exception { InitUtil.initWithSpring(); }
This would work, but it will use the hybersonic database which is defined in the default portal.properties of the portal-impl.jar library. Perhaps its better to use a different database (one that you can control). To do so, you will need to create a "portal-ext.properties" file in the "test" directory, like this:
jdbc.default.driverClassName=com.mysql.jdbc.Driver
jdbc.default.url=jdbc:mysql://localhost:3306/liferay?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false
jdbc.default.username=root
jdbc.default.password=root
hibernate.cache.use_query_cache=false
hibernate.cache.use_second_level_cache=false
hibernate.cache.use_minimal_puts=false
hibernate.cache.use_structured_entries=false
ehcache.portal.cache.manager.jmx.enabled=false
liferay.home="path to liferay portal home dir"
resource.repositories.root="path to app server home dir"/webapps/ROOT
# Disable the scheduler for Unit testing
scheduler.enabled=false
where:
- jdbc.default* define the connection to the database
- hibernate.cache* disables some hibernate stuff like the cache
- "path to liferay portal home dir" is the path to your portal
Then write your integration test. Here a simple test:
@Test public void test() { try { // returns all users of the portal List<User> users = UserLocalServiceUtil.getUsers(QueryUtil.ALL_POS, QueryUtil.ALL_POS); assertTrue("Users must not be empty", !users.isEmpty()); } catch (SystemException e) { fail("Exception:" + e.getMessage()); } }
And now you have your integration test working. After calling "ant integration" you get this:
...
[junit] INFO [main][DialectDetector:71] Determine dialect for MySQL 5
[junit] INFO [main][DialectDetector:136] Found dialect org.hibernate.dialect.MySQLDialect
[junit] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 34.456 sec
[junit] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 34.456 sec
Integration Testing with custom Plugin Services
Will follow in a future update of the article.
I posted in liferay forum the main problem I am facing...
Update: Using Arquillian solves the problem with the integration tests.