dmitrievanthony
dmitrievanthony

Reputation: 1561

JUnit Liferay services

I use liferay service builder in my project and now i want to test *Util classes. It would be easy, but i don't know simple method of init environment. For example in ant testing with spring configuration from service.xml (auto generated) i use InitUtil.initWithSpring() for init beans, but get follow error:

[junit] Tests run: 1, Failures: 0, Errors: 1, Time elapsed: 2,413 sec
[junit] Tests run: 1, Failures: 0, Errors: 1, Time elapsed: 2,413 sec
[junit] 
[junit] Testcase: testJournalArticleSearch(MTest):  Caused an ERROR
[junit] BeanLocator has not been set for servlet context My-portlet
[junit] com.liferay.portal.kernel.bean.BeanLocatorException: BeanLocator has not been set for servlet context My-portlet
[junit]     at com.liferay.portal.kernel.bean.PortletBeanLocatorUtil.locate(PortletBeanLocatorUtil.java:42)
[junit]     at com.my.service.EntityLocalServiceUtil.getService(EntityLocalServiceUtil.java:70)
[junit]     at MTest.setUp(MTest.java:21)

I've seen a few articles on this problem, but it doesn't work or i don't understand these articles... Somebody knows a simple solution to this problem?

Upvotes: 1

Views: 3172

Answers (2)

Mark
Mark

Reputation: 18807

I use Mockito and PowerMock for mocking the Liferay services. The PowerMock allows to mock static methods like XXXLocalServiceUtil. In the linked answer from Prakash K, you can find detailed description: Testing for custom plugin portlet: BeanLocatorException and Transaction roll-back for services testing

Upvotes: 0

Olaf Kock
Olaf Kock

Reputation: 48067

I'm writing this as an answer - it would be more a comment, but the formatting options and length of an answer are what I'm going after.

I frequently see that people have problems writing unit tests for generated code - and *Util, together with servicebuilder, sounds like generated code (*LocalServiceUtil) to me.

My advice is to rather test your *LocalServiceImpl code and trust that the codegenerator is correct (or trust that the codegenerator tests will catch mistakes in there, but this is outside of your scope). After all, the functionality that servicebuilder's *LocalServiceUtil classes deliver is an indirection that looks up the correct implementation (based on spring configuration) and delegate to it. There's no business logic in *LocalServiceUtil classes - this is in *LocalServiceImpl classes.

The next point is: Sometimes even the *Impl-classes are hard to test, because they reach out to other services, which would need to be mocked. In this case - to keep the unit tests readable and independent of the database - I'm proposing to test a layer of code that doesn't reach out to other services. To pick on the code I stole from this answer, here's how I'd rather test it, excluding UserLocalService from the equation (caution: pseudocode, never saw a compiler, I'm editing in this input field)

The code we're about to test is:

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

My Test for this would be ruling out UserLocalService:

@Test
public void testIsUserFullAge() throws Exception {
    //setup (having it here for brevity of the code sample)
    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);

    //run
    asertTrue(MyUserUtil.isUserFullAge(mockUserThatIsFullAge));
    asertFalse(MyUserUtil.isUserFullAge(mockUserThatIsNotFullAge));
}

The important part here is: Your code works on a User object, not on a user Id. Thus you don't need to test the lookup. If you desperately want to test the lookup as well (e.g. test on a broader scale), call it integration test. But don't complain if it breaks often because of some unrelated changes. Because now the reasons for your test to fail are of two different sources: The lookup fails OR your implementation is incorrect. You want your UNIT test to fail for exactly one of the reasons, e.g. immediately know what went wrong when the test fails, not start debugging.

Oh, and yes, that test will start to fail in 2018, in real life I'd test more corner cases, e.g. someone who turns 18 tomorrow or did so yesterday), but this is a different topic.

Upvotes: 3

Related Questions