Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JUnit and Mocks in Liferay

I need to make JUnit tests using Mockito or PowerMock or smth else but I don't know what to start with. I created testing folder, set mockito, but what should I do next? I couldn't find any examples so Im stucked with it. Can you show me how to write this JUnit test or at least give some idea.

public void deleteAuthor(ActionRequest actionRequest, ActionResponse actionResponse)
        throws SystemException, PortalException {
    long authorId = ParamUtil.getLong(actionRequest, "authorId");
    AuthorLocalServiceUtil.deleteAuthor(authorId);
    SessionMessages.add(actionRequest, "deleted-author");
    log.info(DELETE_SUCCESS);

}

Or this:

public void addAuthor(ActionRequest actionRequest, ActionResponse actionResponse) 
        throws IOException, PortletException, SystemException {

    String authorName=ParamUtil.getString(actionRequest,"authorName");
    Author author=AuthorLocalServiceUtil.createAuthor(CounterLocalServiceUtil.increment());
    author.setAuthorName(authorName);
    author=AuthorLocalServiceUtil.addAuthor(author);        
}

P.S. Im very newbie and made only 1 JUnit test in my life, so Im really intrested in good advice. Thanks in advance!


UPD:

I try do to smth like this:

private BookAndAuthor portlet;

@Before
public void setUp() {
    portlet = new BookAndAuthor();
}


@Test
public void testDeleteBookOk() throws Exception {
    PowerMockito.mockStatic(BookLocalServiceUtil.class);
    long id = 1;
    Book book = BookLocalServiceUtil.createBook(id);

    ActionRequest actionRequest = mock(ActionRequest.class);
    ActionResponse actionResponse = mock(ActionResponse.class);

    when(BookLocalServiceUtil.deleteBook(book)).thenReturn(null);
    Book result = BookLocalServiceUtil.deleteBook(book);
    assertEquals(result, null);
}

...but with no success.

like image 702
Al.Boldyrev Avatar asked Sep 07 '16 16:09

Al.Boldyrev


2 Answers

We are running JUnit test using following set-up:

i. Create test folder beside docroot in your portlet.

ii. Add unit folder to test and create your package in it.

iii. Create portal-ext.properties file in your test folder with following configuration:

jdbc.default.driverClassName=com.mysql.jdbc.Driver
jdbc.default.url=jdbc:mysql://localhost:3309/db_name?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false
jdbc.default.username=your_username
jdbc.default.password=your_password

jdbc.default.automaticTestTable=C3P0TestTable
jdbc.default.idleConnectionTestPeriod=36000
jdbc.default.maxIdleTime=1200

iv. Create a suite class (say AbcSuite.java) as following:

package x.x.x;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;

import com.liferay.portal.util.InitUtil;

@RunWith(Suite.class)
@Suite.SuiteClasses({
    // Where AxTest.class would be your test class name
    A1Test.class, A2Test.class, AxTest.class
})

public class AbcSuite {

    @BeforeClass
    public static void setUp() throws Exception {
        // Loading properties and establishing connection with database
        InitUtil.initWithSpring();
        System.out.println("X Portlet's Test Suite Execution : Started.");
    }

    @AfterClass
    public static void tearDown() {
        System.out.println("X Portlet's Test Suite Execution : Completed.");
    }
}

v. Create a test class (say A1Test.java) as following:

package x.x.x;

import java.util.ArrayList;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

public class A1Test {

    @BeforeClass
    public static void setUp() throws Exception {
        System.out.println("Test Running : A1Test");
    }

    @Test
    public void testAddAuthor() {
        Author author = AuthorLocalServiceUtil.createAuthor(
            CounterLocalServiceUtil.increment());
        author.setAuthorName("Testcase Author");
        author = AuthorLocalServiceUtil.addAuthor(author);

        Assert.assertNotNull(author);
        Assert.assertTrue(author.getAuthorId() > 0);
    }
}

That it! You can execute all test cases together using following command:

ant test -Dtest.class=AbcSuite*

or separately as:

ant test -Dtest.class=A1Test*
like image 193
Parkash Kumar Avatar answered Nov 06 '22 20:11

Parkash Kumar


This will be an unpopular answer, but...

I have found that JUnit tests with a lot of mocking objects are not particularly useful. The balance comes in when looking at the size of the setUp() method of your test: The longer it is, the less value the test has. In the portlet world you'd have to use a lot of mocks, and you'll be more busy mirroring the runtime environment (and correcting the assumptions you made about it) than you are fixing issues that you only found during the creation of this kind of tests.

That being said, here's my prescription

  1. Build your portlets with one thing in mind: Portlets are a UI technology. UI is inherently hard to test automatically. You're stuck between the JSR-286 standard and your business layer - two layers that probably don't lend themselves particularly well for connecting them in tests.

  2. Keep your UI layer code so ridiculously simple, that you can go with just a bit of code review. You'll learn more from it than from humongous setUp() routines of your JUnit tests.

  3. Factor out meaningful UI-layer code. Extract it into its own utility class or method. Test that - notice that you probably don't even need a full PortletRequest object for it, use just the actual data that it needs

  4. Create Integration tests on top of all this. These will utilize the full stack, your application deployed in a test environment. They will provide a smoke test to see if your code is actually working. But make sure that testing correct wiring doesn't slow you down: Code of the complexity object.setStreet(request.getParameter("street")); should not be tested, rather code reviewed - and it should be either obviously right or obviously wrong.

  5. Use proper coding standards to make reviews easier. E.g. name your input field "street" if that's the data it holds, not "input42"

With these in mind: Whenever you write a portlet with code that you believe should be tested: Extract it. Eliminate the need to mock the portlet objects or your business layer. Test the extracted code. A second { code block } within a portlet's method might be enough code smell to justify extraction to a separate class/method that can typically be tested trivially - and these tests will be totally independent of Liferay, teach you a lot about your code if they fail, and are far easier to understand than those that set up a lot of mock objects.

I'd rather err on the side of triviality of tests than on the side of too complex tests: Too complex tests will slow you down, rather than provide meaningful insight. They typically only fail because an assumption about the runtime environment was false and needs to be corrected.

like image 34
Olaf Kock Avatar answered Nov 06 '22 19:11

Olaf Kock