Testing for custom plugin portlet: BeanLocatorException and Transaction roll-back for services testing
Asked Answered
S

2

10

My Problems:

  1. I can test successfully for CRUD services operation. I was doing an insert on @Before [setUp()] and delete of same data on @After [tearDown()] but going forward I would need to support Transactions rather than writing code for insert and delete.

  2. I am successful in fetching single records of my entity but when I fire a search query or try to fetch more than one of my entities I get:

    com.liferay.portal.kernel.bean.BeanLocatorException: BeanLocator has not been set for servlet context MyCustom-portlet

I have followed some of the following links to set-up Junit with Liferay:

My Enviroment

  • Liferay 6.0.5 EE bundled with Tomcat

  • Eclipse Helios with Liferay IDE 1.4 using Junit4

  • I am running my tests with "ant" command in eclipse itself but not through typing Alt+Shift+X, T.

It would be really helpful if I can get some idea as to how to go about using Transactions with JUnit (or at least some ideas as to how it works in liferay) and how to resolve the BeanLocatorException (or at least why would it be thrown)

Any help will be greatly appreciated.

Solidarity answered 14/3, 2012 at 11:59 Comment(1)
Anybody out there who knows something about how transactions work in liferay not necessarily in test cases, even a small hint would be useful or an URL or an ebook. Thank YouSolidarity
I
5

I use for JUnit testing mockito framework and inject the services over PortalBeanLocatorUtil.setBeanLocator(...)-methode. I think that is clearly as to do this with spring configuration. Here you have full example how it can be used. The example is shot and that is good so, because the approach is simple and understandable.

package mst.unittest.example;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.junit.Before;
import org.junit.Test;

import com.liferay.portal.kernel.bean.BeanLocator;
import com.liferay.portal.kernel.bean.PortalBeanLocatorUtil;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.model.User;
import com.liferay.portal.service.UserLocalService;
import com.liferay.portal.service.UserLocalServiceUtil;

import static org.junit.Assert.*;

import static org.mockito.Mockito.*;

/**
 * @author [email protected]
 */
public class MyUserUtilTest {


    private BeanLocator mockBeanLocator;

    @Before
    public void init()  {
        //create mock for BeanLocator, BeanLocator is responsible for loading of Services
        mockBeanLocator = mock(BeanLocator.class);
        //... and insert it in Liferay loading infrastructure (instead of Spring configuration)
        PortalBeanLocatorUtil.setBeanLocator(mockBeanLocator);
    }

    @Test
    public void testIsUserFullAge() throws PortalException, SystemException, ParseException {
        //setup
        SimpleDateFormat format = new SimpleDateFormat("yyyy_MM_dd");
        Date D2000_01_01 = format.parse("2000_01_01");
        Date D1990_06_30 = format.parse("1990_06_30");
        UserLocalService mockUserLocalService = mock(UserLocalService.class);
        User mockUserThatIsFullAge = mock(User.class);
        when(mockUserThatIsFullAge.getBirthday()).thenReturn(D1990_06_30);
        User mockUserThatIsNotFullAge = mock(User.class);
        when(mockUserThatIsNotFullAge.getBirthday()).thenReturn(D2000_01_01);
        //overwrite getUser(...) methode so that wir get mock user-object with mocked behavior
        when(mockUserLocalService.getUser(1234567)).thenReturn(mockUserThatIsFullAge);
        when(mockUserLocalService.getUser(7654321)).thenReturn(mockUserThatIsNotFullAge);

        //load our mock-object instead of default UserLocalService
        when(mockBeanLocator.locate("com.liferay.portal.service.UserLocalService")).thenReturn(mockUserLocalService);


        //run
        User userFullAge = UserLocalServiceUtil.getUser(1234567);
        boolean fullAge = MyUserUtil.isUserFullAge(userFullAge);

        //verify
        assertTrue(fullAge);

        //run
        User userNotFullAge = UserLocalServiceUtil.getUser(7654321);
        boolean notfullAge = MyUserUtil.isUserFullAge(userNotFullAge);

        //verify
        assertFalse(notfullAge);
    }

}

class MyUserUtil {

    public static boolean isUserFullAge(User user) throws PortalException, SystemException {
        Date birthday = user.getBirthday();
        long years = (System.currentTimeMillis() - birthday.getTime()) / ((long)365*24*60*60*1000);
        return years > 18;
    }

}

You can use this approach also without mockito framework, then you must create the mock-classes like MockBeanLocator manually.

Approach with PowerMock

With PowerMock you can to abdicate BeanLocator because PowerMock allows to override static methods. Here the same example with PowerMock:

package mst.unittest.example;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

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;

import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.model.User;
import com.liferay.portal.service.UserLocalServiceUtil;

import static org.junit.Assert.*;

import static org.mockito.Mockito.*;

/**
 * @author Mark Stein
 *
 */

@RunWith(PowerMockRunner.class)
@PrepareForTest(UserLocalServiceUtil.class)
public class LiferayAndPowerMockTest {

    @Test
    public void testIsUserFullAge() throws PortalException, SystemException, ParseException {
        //setup
        SimpleDateFormat format = new SimpleDateFormat("yyyy_MM_dd");
        Date D2000_01_01 = format.parse("2000_01_01");
        Date D1990_06_30 = format.parse("1990_06_30");
        User mockUserThatIsFullAge = mock(User.class);
        when(mockUserThatIsFullAge.getBirthday()).thenReturn(D1990_06_30);
        User mockUserThatIsNotFullAge = mock(User.class);
        when(mockUserThatIsNotFullAge.getBirthday()).thenReturn(D2000_01_01);

        //overwrite getUser(...) by UserLocalServiceUtil  methode so that wir get mock user-object with mocked behavior
        PowerMockito.mockStatic(UserLocalServiceUtil.class);
        when(UserLocalServiceUtil.getUser(1234567)).thenReturn(mockUserThatIsFullAge);
        when(UserLocalServiceUtil.getUser(7654321)).thenReturn(mockUserThatIsNotFullAge);

        //run
        boolean fullAge = MySecUserUtil.isUserFullAge(1234567);

        //verify
        assertTrue(fullAge);

        //run

        boolean notfullAge = MySecUserUtil.isUserFullAge(7654321);

        //verify
        assertFalse(notfullAge);
    }

}

class MySecUserUtil {

    public static boolean isUserFullAge(long userId) throws PortalException, SystemException {
        User user = UserLocalServiceUtil.getUser(userId);
        Date birthday = user.getBirthday();
        long years = (System.currentTimeMillis() - birthday.getTime()) / ((long)365*24*60*60*1000);
        return years > 18;
    }

}

Here you found PowerMock 1.4.12 with Mockito and JUnit including dependencies http://code.google.com/p/powermock/downloads/detail?name=powermock-mockito-junit-1.4.12.zip&can=2&q=

Inveigh answered 8/8, 2012 at 0:38 Comment(7)
Thanks a lot! This gives me sunshine on this unit-testing thing. So will this work if I use my com.prakash.MyLocalService class instead of com.liferay.portal.service.UserLocalService? I will try this and let you know. Thanks again!Solidarity
Yes, you can do this for own services too, but you need to use PortletBeanLocatorUtil instead of PortalBeanLocatorUtil, or take the PowerMock-addition for Mockito and mock directly the com.prakash.MyLocalServiceUtil-classInveigh
Great! Thanks! It would be great if you can include this information in your answer as well. Thanks Mark.Solidarity
I've used your code for a suggestion to test less (e.g. no UserLocalService call) here: https://mcmap.net/q/1167314/-junit-liferay-services/…Bombardier
Here we are StackOverflow community, and I'm glad if my code is useful :)Inveigh
... and I home that Prakash K found time to proof my approach ;)Inveigh
:-) Mark, sure here you go ... Have not tested with transactions though. But I guess that would be a different topic. Accepting the answer for now ... Thanks. Also Thanks @OlafKock for the link to that answer.Solidarity
G
2

Speculation: do you really need to test the transaction? Or just the business logic around the db access? Because if so, you could try writing the unit test with EasyMock (or similar), avoiding the access to the database yet testing the functionality

Genteel answered 21/6, 2012 at 16:52 Comment(3)
Will try that out. Thanks for the suggestion. I do need to test transactions though, but I guess that can wait :-). Do you have any idea of using it with liferay?Solidarity
Are you using ServiceBuilder for your entities? If so, I think (not 100% sure) you wont be able to do so. My experience with Liferay tells me that every call you make to a service is a transaction. Even Liferay works this way on its backend. If I were you I would try to set up my own Spring context (and not Liferay's), set up Hibernate (you can make it use liferay's data source) and go from there. Design your service with a @Transactional aspect and test your transactionsGenteel
I am using liferay's Service Builder. "My experience with Liferay tells me that every call you make to a service is a transaction" - call through *ServiceUtil class, then Yes. But we can call multiple service methods through the instances of *service available to us. I was thinking Service builder uses Spring Context and hibernate.Solidarity

© 2022 - 2024 — McMap. All rights reserved.