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:

  1. Infrastructure: the folder structure should follow specific names, so that the sdk ant tool can find everything in tis place
  2. Unit Testing: a simple unit test
  3. Unit Testing with mockups: a more advanced unit test using a mockup library
  4. Integration Testing with Liferay core Services
  5. 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:

1
2
3
4
5
6
7
8
9
10
11
+-- 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 unit : runs all unit tests
  • amt integration: runs all integration tests

After running one of the above commands for the first time, two new directories will be created, like this:

1
2
3
4
5
6
7
8
9
+-- 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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:

1
2
3
4
5
6
7
8
9
10
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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:

1
2
3
4
    [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:

1
2
3
4
5
6
7
    @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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
    @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:

1
2
3
4
5
    ...
    [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…

“PermissionChecker not initialised” in scheduler job

When writing a scheduler job in liferay, you don’t have access to the the themeDisplay life in portlet methods. So you miss a lot of handy data.

If you like to call in your job code some of the local utility classes, you could become the following exception:

1
com.liferay.portal.security.auth.PrincipalException: PermissionChecker not initialized

This is because you don’t have the permission to make that call. Just use the following code before your code and everything will be fine.

1
2
3
4
5
6
7
8
9
Company companyqq = CompanyLocalServiceUtil.getCompanyByWebId("myCompanyWebId");
Role adminRole = RoleLocalServiceUtil.getRole(companyqq.getCompanyId(),"Administrator");
List<User> adminUsers = UserLocalServiceUtil.getRoleUsers(adminRole.getRoleId());

PrincipalThreadLocal.setName(adminUsers.get(0).getUserId());
PermissionChecker permissionChecker =PermissionCheckerFactoryUtil.create(adminUsers.get(0), true);
PermissionThreadLocal.setPermissionChecker(permissionChecker);

myMethod();

source: here

Custom simple captcha in custom portlet for Liferay

In the last month we had to add different captchas in some liferay sites. Some customers were ok with the simple captcha, that comes per default with liferay and some others wanted to have the recaptcha of google, that also comes with liferay (not so default). And there was a third category of clients that weren’t happy with a any of the above mentioned captchas and wanted a third solution.

In this post we will show how to add simple captcha in a form. To integrate simple catch in a form of a custom portlet is described in many articles like this one.

But there is not so many articles that describe how to make a captcha with a custom design and a refresh image button. So, here is a why way to implement it:

In the portlet class we need to change the code in two positions.

First in the serveResource method:

1
2
3
public void serveResource(ResourceRequest request, ResourceResponse response) throws PortletException, IOException {
  CaptchaUtil.serveImage(request, response);
}

The code above returns the image of the captcha.

And second in the action method:

1
2
3
4
5
6
7
8
9
10
public void processAction(ActionRequest actionRequest, ActionResponse actionResponse) throws IOException {
  try {
    CaptchaUtil.check(actionRequest);
  } catch (CaptchaTextException e) {
    SessionErrors.add(actionRequest, CaptchaTextException.class.getName());
  } catch (CaptchaMaxChallengesException e) {
    SessionErrors.add(actionRequest, CaptchaMaxChallengesException.class.getName());
  }
  ...
}

The code above checks the value of the captcha value and throws exceptions if the input is invalid.

Next, the jsp code. We need to write the html that shows the image, the refresh button and the text field. In addition, we need a javascript for the refresh button to replace the image with the new one.

Of course, our code is an example of how the html could be. Its is not a ready to use design.

First, the html:

1
2
3
4
5
6
7
8
9
10
11
12
13
  <%-- The url that returns the image --%>
  <portlet:resourceURL var="captchaURL"></portlet:resourceURL>
  <label><liferay-ui:message key="captcha-text" /></label>
  <div style="float:left;">
    <%-- the name of the field should be captchaText --%>
    <aui:input label="" name="captchaText" size="10" type="text" value="">
      <aui:validator name="required" />
    </aui:input>
  </div>
  <%-- the refresh button %-->
  <a href="#" class="refreshCaptcha captcha-reload" style="text-decoration: none;"></a>
  <%-- the image --%>
  <img style="float:left;padding:0px !important;margin-left:10px;" alt='<liferay-ui:message key="text-to-identify" />' class="captcha" border=0 src="<%= captchaURL + "&" + new Random().nextInt(Integer.MAX_VALUE) %>" />

And the js code:

1
2
3
4
jQuery(".refreshCaptcha").click(function(evt){
  jQuery(".captcha").attr('src', '<%=captchaURL%>&force='+encodeURIComponent(Math.floor(Math.random()*Math.pow(2, 53))));
  evt.preventDefault();
});

A tip to solve many asset category problems

In case you are experiencing a strange behaviour in the Categories portlet of the control panel, like creating a category in vocabulary x and finding it in vocabulary y, you try to rebuild the category tree for this site.

The API of the liferay gives us a method for that job: AssetCategoryLocalServiceUtil.rebuildTree

The method has 2 parameters, but you need to find out the value only for the first one. This parameter is the groupId, which is the id of the site. If you don’t know it you can find it following the steps as described here.

But if you are not a programmer, how can execute this method? The solution is simple. As the Administrator, go to the “Server Administration” and then to the “script” tab. Here you can run scripts in different languages. Select “Beanshell” and in the text area give the following code:

1
2
import com.liferay.portlet.asset.service.AssetCategoryLocalServiceUtil;
AssetCategoryLocalServiceUtil.rebuildTree(type_here_the_groupId, true);

and then type run.

Original PortletConfig in Portlet Configuration

Lets assume you have a Project with its own Language properties and a portlet that have a configuration page.

Normally, if you want to get a literal value from the language properties in the doView method of your portlet you can use the following code:

1
String value = LanguageUtil.get(getPortletConfig(), themeDisplay.getLocale(), "literal-key");

But if you want to do the same in the configuration render method then you will encounter a problem with the portletConfig object, that comes as a parameter in the method.

1
2
3
4
public String render(PortletConfig config, RenderRequest request, RenderResponse response) {
    String value = LanguageUtil.get(config, themeDisplay.getLocale(), "literal-key");
    // ...
}

The reason is that the configuration is a portlet from its own, and the portletConfig that you get is the config of the configuration portlet and not from the original portlet. And the configuration portlet is is not part of your plugin project, so it can’t see the custom language properties that you have.

The solution is simple. Just get the portletConfig of your original portlet. For example use the following code:

1
2
3
4
5
6
String origPortletResource = ParamUtil.getString(request, "portletResource");
HttpServletRequest servletRequest = PortalUtil.getHttpServletRequest(request);
ServletContext servletContext = servletRequest.getSession().getServletContext();
Portlet origPortlet = PortletLocalServiceUtil.getPortletById(origPortletResource);
PortletConfig origConfig = PortletConfigFactoryUtil.create(origPortlet, servletContext);
String value = LanguageUtil.get(origConfig, themeDisplay.getLocale(), "literal-key");

How to define an entity in Service Builder for existing tables

Recently we encounter a case, where the tables of an external database were “moved” as synonyms in the schema of the liferay db. This was made to eliminate the extra connections in the same database but on a different schema (liferay’s service builder can’t handle different schemas in the same database with one datasource. You have to define a new datasource as an external database).

So, having now the tables in the liferay db, we can defined the entities in the service.xml as normal entities. But this has a side effect: liferay will try to create the tables in case they don’t exist. And if they are synonyms, liferay will throw an error and say that another object (the synonym) has the same name with the table that is about to create.

There is a workaround: you can create a fake datasource and define it in the entity in service.xml. This datasource can return the null value, so the service builder will think it is an external db and won’t create any scripts for this entity. But because the datasource is null, the default datasource will be used.