Showing posts with label Test. Show all posts
Showing posts with label Test. Show all posts

Sunday, April 3, 2016

Clean architecture of Selenium tests

In this blog post, I would like to introduce a clean architecture for Selenium tests with best design patterns: page object, page element (often called HTML wrapper) and self-developed, very small but smart framework. The architecture is not restricted to Java which is used in the examples and can be applied to Selenium tests in any other language as well.

Definitions and relations.

Page Object. A page object encapsulates the behavior of a web page. There is one page object per web page that abstracts the page's logic to the outside. That means, the interaction with the web page is encapsulated in the page object. Selenium's By locators to find elements on the page are not disclosed to the outside as well. The page object's caller should not be busy with the By locators, such as By.id, By.tageName, By.cssSelector, etc. Selenium test classes operate on page objects. Take an example from a web shop: the page object classes could be called e.g. ProductPage, ShoppingCartPage, PaymentPage, etc. These are always classes for the whole web pages with their own URLs.

Page Element (aka HTML Wrapper). A page element is another subdivision of a web page. It represents a HTML element and encapsulates the logic for the interaction with this element. I will term a page element as HTML wrapper. HTML wrappers are reusable because several pages can incorporate the same elements. For instance, a HTML wrapper for Datepicker can provide the following methods (API): "set a date into the input field", "open the calendar popup", "choose given day in the calendar popup", etc. Other HTML wrappes would be e.g. Autocomplete, Breadcrumb, Checkbox, RadioButton, MultiSelect, Message, ... A HTML Wrapper can be composite. That means, it can consist of multiple small elements. For instance, a product catalog consists of products, a shopping cart consists of items, etc. Selenium's By locators for the inner elements are encapsulated in the composite page element.

Page Object and HTML Wrappers as design patterns were described by Martin Fowler.

The skeletal structure of a Selenium test class.

A test class is well structured. It defines the test sequence in form of single process steps. I suggest the following structure:
public class MyTestIT extends AbstractSeleniumTest {

    @FlowOnPage(step = 1, desc = "Description for this method")
    void flowSomePage(SomePage somePage) {
        ...
    }

    @FlowOnPage(step = 2, desc = "Description for this method")
    void flowAnotherPage(AnotherPage anotherPage) {
        ...
    }

    @FlowOnPage(step = 3, desc = "Description for this method")
    void flowYetAnotherPage(YetAnotherPage yetAnotherPage) {
        ...
    }

    ...
}
The class MyTestIT is an JUnit test class for an integration test. @FlowOnPage is a method annotation for the test logic on a web page. The step parameter defines a serial number in the test sequence. The numeration starts with 1. That means, the annotated method with the step = 1 will be processed before the method with the step = 2. The second parameter desc stands for description what the method is doing.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FlowOnPage {

    int step() default 1;
 
    String desc();
}
The annotated method is invoked with a page object as method parameter. A switch to the next page normally occurs per click on a button or link. The developed framework should make sure that the next page is completely loaded before the annotated method with next step gets called. The next diagram illustrates the relationship between a test class, page objects and HTML wrappers.


But stop. Where is the JUnit method annotated with @Test and where is the logic for the parsing of @FlowOnPage annotation? That code is hidden in the super class AbstractSeleniumTest.
public abstract class AbstractSeleniumTest {

    // configurable base URL
    private final String baseUrl = System.getProperty("selenium.baseUrl", "http://localhost:8080/contextRoot/");

    private final WebDriver driver;

    public AbstractSeleniumTest() {
        // create desired WebDriver
        driver = new ChromeDriver();

        // you can also set here desired capabilities and so on
        ...
    }

    /**
     * The single entry point to prepare and run test flow.
     */
    @Test
    public void testIt() throws Exception {
        LoadablePage lastPageInFlow = null;
        List <Method> methods = new ArrayList<>();

        // Seach methods annotated with FlowOnPage in this and all super classes
        Class c = this.getClass();
        while (c != null) {
            for (Method method: c.getDeclaredMethods()) {
                if (method.isAnnotationPresent(FlowOnPage.class)) {
                    FlowOnPage flowOnPage = method.getAnnotation(FlowOnPage.class);
                    // add the method at the right position
                    methods.add(flowOnPage.step() - 1, method);
                }
            }

            c = c.getSuperclass();
        }

        for (Method m: methods) {
            Class<?>[] pTypes = m.getParameterTypes();

            LoadablePage loadablePage = null;
            if (pTypes != null && pTypes.length > 0) {
                loadablePage = (LoadablePage) pTypes[0].newInstance();
            }

            if (loadablePage == null) {
                throw new IllegalArgumentException("No Page Object as parameter has been found for the method " +
                    m.getName() + ", in the class " + this.getClass().getName());
            }

            // initialize Page Objects Page-Objekte and set parent-child relationship
            loadablePage.init(this, m, lastPageInFlow);
            lastPageInFlow = loadablePage;
        }

        if (lastPageInFlow == null) {
            throw new IllegalStateException("Page Object to start the test was not found");
        }

        // start test
        lastPageInFlow.get();
    }

    /**
     * Executes the test flow logic on a given page.
     *
     * @throws AssertionError can be thrown by JUnit assertions
     */
    public void executeFlowOnPage(LoadablePage page) {
        Method m = page.getMethod();
        if (m != null) {
            // execute the method annotated with FlowOnPage
            try {
                m.setAccessible(true);
                m.invoke(this, page);
            } catch (Exception e) {
                throw new AssertionError("Method invocation " + m.getName() +
                    ", in the class " + page.getClass().getName() + ", failed", e);
            }
        }
    }

    @After
    public void tearDown() {
        // close browser
        driver.quit();
    }

    /**
     * This method is invoked by LoadablePage.
     */
    public String getUrlToGo(String path) {
        return baseUrl + path;
    }

    public WebDriver getDriver() {
        return driver;
    }
}
As you can see, there is only one test method testIt which parses the annotations, creates page objects with relations and starts the test flow.

The structure of a Page Object.

Every page object class inherits from the class LoadablePage which inherits again from the Selenium's class LoadableComponent. A good explanation for LoadableComponent is available in this well written article: Simple and advanced usage of LoadableComponent. LoadablePage is our own class, implemented as follows:
import org.openqa.selenium.support.ui.WebDriverWait;
import org.junit.Assert;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.LoadableComponent;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.List;

public abstract class LoadablePage<T extends LoadableComponent<T>> extends LoadableComponent<T> {

    private final static Logger LOGGER = LoggerFactory.getLogger(LoadablePage.class);

    private AbstractSeleniumTest seleniumTest;
    private String pageUrl;
    private Method method;
    private LoadablePage parent;

    /**
     * Init method (invoked by the framework).
     *
     * @param seleniumTest instance of type AbstractSeleniumTest
     * @param method to be invoked method annotated with @FlowOnPage
     * @param parent parent page of type LoadablePage
     */
    void init(AbstractSeleniumTest seleniumTest, Method method, LoadablePage parent) {
        this.seleniumTest = seleniumTest;
        this.pageUrl = seleniumTest.getUrlToGo(getUrlPath());
        this.method = method;
        this.parent = parent;

        PageFactory.initElements(getDriver(), this);
    }

    /**
     * Path of the URL without the context root for this page.
     *
     * @return String path of the URL
     */
    protected abstract String getUrlPath();

    /***
     * Specific check which has to be implemented by every page object.
     * A rudimentary check on the basis of URL is undertaken by this class.
     * This method is doing an extra check if the page has been proper loaded.
     *
     * @throws Error thrown when the check fails
     */
    protected abstract void isPageLoaded() throws Error;

    @Override
    protected void isLoaded() throws Error {
        // min. check against the page URL
        String url = getDriver().getCurrentUrl();
        Assert.assertTrue("You are not on the right page.", url.equals(pageUrl));

        // call specific check which has to be implemented on every page
        isPageLoaded();
    }

    @Override
    protected void load() {
        if (parent != null) {
            // call the logic in the parent page
            parent.get();

            // parent page has navigated to this page (via click on button or link).
            // wait until this page has been loaded.
            WebDriverWait wait = new WebDriverWait(getDriver(), 20, 250);
            wait.until(new ExpectedCondition<Boolean> () {
                @Override
                public Boolean apply(WebDriver d) {
                    try {
                        isLoaded();
                        return true;
                    } catch (AssertionError e) {
                        return false;
                    }
                }
            });
        } else {
            // Is there no parent page, the page should be navigated directly
            LOGGER.info("Browser: {}, GET {}", getDriver(), getPageUrl());
            getDriver().get(getPageUrl());
        }
    }

    /**
     * Ensure that this page has been loaded and execute the test code on the this page.
     *
     * @return T LoadablePage
     */
    public T get() {
        T loadablePage = super.get();

        // execute flow logic
        seleniumTest.executeFlowOnPage(this);

        return loadablePage;
    }

    /**
     * See {@link WebDriver#findElement(By)}
     */
    public WebElement findElement(By by) {
        return getDriver().findElement(by);
    }

    /**
     * See {@link WebDriver#findElements(By)}
     */
    public List<WebElement> findElements(By by) {
        return getDriver().findElements(by);
    }

    public WebDriver getDriver() {
        return seleniumTest.getDriver();
    }

    protected String getPageUrl() {
        return pageUrl;
    }

    Method getMethod() {
        return method;
    }
}
As you can see, every page object class needs to implement two abstract methods:
/**
 * Path of the URL without the context root for this page.
 *
 * @return String path of the URL
 */
protected abstract String getUrlPath();

/***
 * Specific check which has to be implemented by every page object.
 * A rudimentary check on the basis of URL is undertaken by the super class.
 * This method is doing an extra check if the page has been proper loaded.
 *
 * @throws Error thrown when the check fails
 */
protected abstract void isPageLoaded() throws Error;
Now I would like to show the code for a concrete page object and a test class which tests the SBB Ticket Shop, so that readers can acquire a taste for testing with page objects. The page object TimetablePage contains HTML wrappers for basic elements.
public class TimetablePage extends LoadablePage<TimetablePage> {

    @FindBy(id = "...")
    private Autocomplete from;
    @FindBy(id = "...")
    private Autocomplete to;
    @FindBy(id = "...")
    private Datepicker date;
    @FindBy(id = "...")
    private TimeInput time;
    @FindBy(id = "...")
    private Button search;

    @Override
    protected String getUrlPath() {
        return "pages/fahrplan/fahrplan.xhtml";
    }

    @Override
    protected void isPageLoaded() throws Error {
        try {
            assertTrue(findElement(By.id("shopForm_searchfields")).isDisplayed());
        } catch (NoSuchElementException ex) {
            throw new AssertionError();
        }
    }

    public TimetablePage typeFrom(String text) {
        from.setValue(text);
        return this;
    }

    public TimetablePage typeTo(String text) {
        to.setValue(text);
        return this;
    }

    public TimetablePage typeTime(Date date) {
        time.setValue(date);
        return this;
    }

    public TimetablePage typeDate(Date date) {
        date.setValue(date);
        return this;
    }

    public TimetablePage search() {
        search.clickAndWaitUntil().ajaxCompleted().elementVisible(By.cssSelector("..."));
        return this;
    }

    public TimetableTable getTimetableTable() {
        List<WebElement> element = findElements(By.id("..."));
        if (element.size() == 1) {
            return TimetableTable.create(element.get(0));
        }

        return null;
    }
}
In the page object, HTML wrappers (simple or composite) can be created either by the @FindBy, @FindBys, @FindAll annotations or dynamic on demand, e.g. as TimetableTable.create(element) where element is the underlying WebElement. Normally, the annotations don't work with custom elements. They only work with the Selenium's WebElement per default. But it is not difficult to get them working with the custom elements too. You have to implement a custom FieldDecorator which extends DefaultFieldDecorator. A custom FieldDecorator allows to use @FindBy, @FindBys, or @FindAll annotations for custom HTML wrappers. A sample project providing implementation details and examples of custom elements is available here. You can also catch the Selenium's infamous StaleElementReferenceException in your custom FieldDecorator and recreate the underlying WebElement by the original locator. A framework user doesn't see then StaleElementReferenceException and can call methods on WebElement even when the referenced DOM element was updated in the meantime (removed from the DOM and added with a new content again). This idea with code snippet is available here.

But ok, let me show the test class. In the test class, we want to test if a hint appears in the shopping cart when a child under 16 years travels without parents. Firts of all, we have to type the stations "from" and "to", click on a desired connection in the timetable and add a child on the next page which shows travel offers for the choosen connection.
public class HintTravelerIT extends AbstractSeleniumTest {

    @FlowOnPage(step = 1, desc = "Seach a connection from Bern to Zürich and click on the first 'Buy' button")
    void flowTimetable(TimetablePage timetablePage) {
        // Type from, to, date and time
        timetablePage.typeFrom("Bern").typeTo("Zürich");
        Date date = DateUtils.addDays(new Date(), 2);
        timetablePage.typeDate(date);
        timetablePage.typeTime(date);

        // search for connections
        timetablePage.search();

        // click on the first 'Buy' button
        TimetableTable table = timetablePage.getTimetableTable();
        table.clickFirstBuyButton();
    }

    @FlowOnPage(step = 2, desc = "Add a child as traveler and test the hint in the shopping cart")
    void flowOffers(OffersPage offersPage) {
        // Add a child
        DateFormat df = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN);
        String birthDay = df.format(DateUtils.addYears(new Date(), -10));
        offersPage.addTraveler(0, "Max", "Mustermann", birthDay);
        offersPage.saveTraveler();

        // Get hints
        List<String> hints = offersPage.getShoppingCart().getHints();

        assertNotNull(hints);
        assertTrue(hints.size() == 1);
        assertEquals("A child can only travel under adult supervision", hints.get(0));
    }
}

The structure of a HTML Wrapper.

I suggest to create an abstract base class for all HTML wrappers. Let's call it HtmlWrapper. This class can provide some common methods, such as click, clickAndWaitUntil, findElement(s), getParentElement, getAttribute, isDisplayed, ... For editable elements, you can create a class EditableWrapper which inherits from the HtmlWrapper. This class can provide some common methods for editable elements, such as clear (clears the input), enter (presses the enter key), isEnabled (checks if the element is enabled), ... All editable elements should inherit from the EditableWrapper. Futhermore, you can provide two interfaces EditableSingleValue and EditableMultipleValue for single and multi value elements respectively. The next diagram demonstrates the idea. It shows the class hierarchy for three basic HTML wrappes:

Datepicker. It inherits from the EditableWrapper and implements the EditableSingleValue interface.
MultiSelect. It inherits from the EditableWrapper and implements the EditableMultiValue interface.
Message. It extends directly the HtmlWrapper because a message is not editable.


Do you want more implementation details for HTML wrappers? Details for an jQuery Datepicker can be found for example in this great article. The MultiSelect is a wrapper around the famous Select2 widget. I have implemented the wrapper in my project in the following way:
public class MultiSelect extends EditableWrapper implements EditableMultiValue<String> {

    protected MultiSelect(WebElement element) {
        super(element);
    }

    public static MultiSelect create(WebElement element) {
        assertNotNull(element);
        return new MultiSelect(element);
    }

    @Override
    public void clear() {
        JavascriptExecutor js = (JavascriptExecutor) getDriver();
        js.executeScript("jQuery(arguments[0]).val(null).trigger('change')", element);
    }

    public void removeValue(String...value) {
        if (value == null || value.length == 0) {
            return;
        }

        JavascriptExecutor js = (JavascriptExecutor) getDriver();
        Object selectedValues = js.executeScript("return jQuery(arguments[0]).val()", element);
        String[] curValue = convertValues(selectedValues);
        String[] newValue = ArrayUtils.removeElements(curValue, value);

        if (newValue == null || newValue.length == 0) {
            clear();
        } else {
            changeValue(newValue);
        }
    }

    public void addValue(String...value) {
        if (value == null || value.length == 0) {
            return;
        }

        JavascriptExecutor js = (JavascriptExecutor) getDriver();
        Object selectedValues = js.executeScript("return jQuery(arguments[0]).val()", element);
        String[] curValue = convertValues(selectedValues);
        String[] newValue = ArrayUtils.addAll(curValue, value);

        changeValue(newValue);
    }

    @Override
    public void setValue(String...value) {
        clear();
        if (value == null || value.length == 0) {
            return;
        }

        changeValue(value);
    }

    @Override
    public String[] getValue() {
        JavascriptExecutor js = (JavascriptExecutor) getDriver();
        Object values = js.executeScript("return jQuery(arguments[0]).val()", element);

        return convertValues(values);
    }

    private void changeValue(String...value) {
        Gson gson = new Gson();
        String jsonArray = gson.toJson(value);

        String jsCode = String.format("jQuery(arguments[0]).val(%s).trigger('change')", jsonArray);
        JavascriptExecutor js = (JavascriptExecutor) getDriver();
        js.executeScript(jsCode, element);
    }

    @SuppressWarnings("unchecked")
    private String[] convertValues(Object values) {
        if (values == null) {
            return null;
        }

        if (values.getClass().isArray()) {
            return (String[]) values;
        } else if (values instanceof List) {
            List<String> list = (List<String> ) values;
            return list.toArray(new String[list.size()]);
        } else {
            throw new WebDriverException("Unsupported value for MultiSelect: " + values.getClass());
        }
    }
}
And an example of Message implementation for the sake of completeness:
public class Message extends HtmlWrapper {

    public enum Severity {
        INFO("info"),
        WARNING("warn"),
        ERROR("error");

        Severity(String severity) {
            this.severity = severity;
        }

        private final String severity;

        public String getSeverity() {
            return severity;
        }
    }

    protected Message(WebElement element) {
        super(element);
    }

    public static Message create(WebElement element) {
        assertNotNull(element);
        return new Message(element);
    }

    public boolean isAnyMessageExist(Severity severity) {
        List<WebElement> messages = findElements(
      By.cssSelector(".ui-messages .ui-messages-" + severity.getSeverity()));
        return messages.size() > 0;
    }

    public boolean isAnyMessageExist() {
        for (Severity severity: Severity.values()) {
            List<WebElement> messages = findElements(
       By.cssSelector(".ui-messages .ui-messages-" + severity.getSeverity()));
            if (messages.size() > 0) {
                return true;
            }
        }

        return false;
    }

    public List<String> getMessages(Severity severity) {
        List<WebElement> messages = findElements(
      By.cssSelector(".ui-messages .ui-messages-" + severity.getSeverity() + "-summary"));
        if (messages.isEmpty()) {
            return null;
        }

        List<String> text = new ArrayList<> ();
        for (WebElement element: messages) {
            text.add(element.getText());
        }

        return text;
    }
}
The Message wraps the Message component in PrimeFaces.

Conclusion: when you finished the writing of page objects and HTML wrappers, you can settle back and concentrate on the comfortable writing of Selenium tests. Feel free to share your thoughts.

Sunday, March 30, 2014

Set up JSF environment for JUnit tests

JUnit tests often need mocked JSF / Servlet objects when you test JSF based web applications. Such objects can be FacesContext, ExternalContext, ApplicationContext, HttpRequest, HttpSession, etc. I already mentioned the MyFaces Test Framework in one of my outdated post. In this post, I would like to introduce a new simple and lightweight approach based on JUnit TestRule. The concept behind TestRule is similar to custom JUnit runners, but without restrictions (you can not use multiple runners, but you can use multiple TestRules). Let's go step by step to explain the idea. A class which implements the interface TestRule must implement the method
Statement apply(Statement base, Description description)
The first Statement parameter is a specific object which reprensents the method under the test from your test class. Such a test method can be invoked by base.evaluate(). You can place any custom code before and after the call base.evaluate(). A typically implementation follows this pattern
public Statement apply(final Statement base, Description description) {
    return new Statement() {
        @Override
        public void evaluate() throws Throwable {
            // do something before invoking the method to be tested
            ...
            try {
                base.evaluate();
            } finally {
                // do something after invoking the method to be tested
                ...
            }
        }
    };
}
In short words: the apply method allows to intercept the base call of every test method and put a custom code around. Your TestRule implementation, say MyRule, can be used in any test class with the @Rule annotation as follows:
@Rule
public TestRule myRule = new MyRule();
Note: The member variable should be public. Let's take more examples. There is a good introduction in this tutorial. The author demonstrates how to implement two TestRules: one for SpringContext to use @Autowired in test classes and one for Mockito to populate the mocks before each test. An excellent example! I allow me to repeat the usage example.
public class FooTest {

    @Rule
    public TestRule contextRule = new SpringContextRule(new String[]{"testContext.xml"}, this);

    @Rule
    public TestRule mockRule = new MockRule(this);

    @Autowired
    public String bar;

    @Mock
    public List baz;

    @Test
    public void testBar() throws Exception {
        assertEquals("bar", bar);
    }

    @Test
    public void testBaz() throws Exception {
        when(baz.size()).thenReturn(2);
        assertEquals(2, baz.size());
    }
}
This can not be achieved with two JUnit runners at once. E.g. you can not annotate a test class at the same time with @RunWith(Parameterized.class) and @RunWith(SpringJUnit4ClassRunner.class) or @RunWith(MockitoJUnitRunner.class).

But back to JSF. I want to show how to implement a TestRule for a simple and extensible JSF environment. First of all, we need a mock for FacesContext. We will implement it with Mockito - the most popular Java test framework. I have seen many different implementations, but in fact it is not difficult to implement a proper mock of FacesContext.
import javax.faces.context.FacesContext;

import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public abstract class FacesContextMocker extends FacesContext {

    private FacesContextMocker() {
    }

    private static final Release RELEASE = new Release();

    private static class Release implements Answer<Void> {
        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
            setCurrentInstance(null);
            return null;
        }
    }

    public static FacesContext mockFacesContext() {
        FacesContext context = Mockito.mock(FacesContext.class);
        setCurrentInstance(context);
        Mockito.doAnswer(RELEASE).when(context).release();
        return context;
    }
}
For all PrimeFaces fan we will provide a similar mock for RequestContext.
import org.primefaces.context.RequestContext;

import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public abstract class RequestContextMocker extends RequestContext {

    private RequestContextMocker() {
    }

    private static final Release RELEASE = new Release();

    private static class Release implements Answer<Void> {
        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
            setCurrentInstance(null);
            return null;
        }
    }

    public static RequestContext mockRequestContext() {
        RequestContext context = Mockito.mock(RequestContext.class);
        setCurrentInstance(context);
        Mockito.doAnswer(RELEASE).when(context).release();
        return context;
    }
}
Now, a minimal JSF / Servlet environment could be set up as follows
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.faces.application.Application;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.mockito.Mockito;
import org.primefaces.context.RequestContext;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class JsfMock implements TestRule {

    public FacesContext mockFacesContext;
    public RequestContext mockRequestContext;
    public UIViewRoot mockViewRoot;
    public Application mockApplication;
    public ExternalContext mockExternalContext;
    public HttpSession mockHttpSession;
    public HttpServletRequest mockHttpServletRequest;
    public HttpServletResponse mockHttpServletResponse;

    @Override
    public Statement apply(final Statement base, final Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                init();
                try {
                    base.evaluate();
                } finally {
                    mockFacesContext.release();
                    mockRequestContext.release();
                }
            }
        };
    }

    protected void init() {
        mockFacesContext = FacesContextMocker.mockFacesContext();
        mockRequestContext = RequestContextMocker.mockRequestContext();
        mockApplication = Mockito.mock(Application.class);
        mockViewRoot = Mockito.mock(UIViewRoot.class);
        mockExternalContext = Mockito.mock(ExternalContext.class);
        mockHttpServletRequest = Mockito.mock(HttpServletRequest.class);
        mockHttpServletResponse = Mockito.mock(HttpServletResponse.class);
        mockHttpSession = Mockito.mock(HttpSession.class);

        Mockito.when(mockFacesContext.getApplication()).thenReturn(mockApplication);
        Mockito.when(mockApplication.getSupportedLocales()).thenReturn(createLocales().iterator());

        Mockito.when(mockFacesContext.getViewRoot()).thenReturn(mockViewRoot);
        Mockito.when(mockViewRoot.getLocale()).thenReturn(new Locale("en"));

        Mockito.when(mockFacesContext.getExternalContext()).thenReturn(mockExternalContext);
        Mockito.when(mockExternalContext.getRequest()).thenReturn(mockHttpServletRequest);
        Mockito.when(mockHttpServletRequest.getSession()).thenReturn(mockHttpSession);

        Map<String, String> requestMap = new HashMap<String, String>();
        Mockito.when(mockExternalContext.getRequestParameterMap()).thenReturn(requestMap);        
    }

    private List<Locale> createLocales() {
        ArrayList<Locale> locales = new ArrayList<>();
        locales.add(new Locale("en"));
        locales.add(new Locale("de"));
        ...
        return locales;
    }
}
We mocked the most used JSF / Servlet objects, linked them with each other and provided mocks via public member variables, so that they can be extended in test classes if needed. Below is an usage example which also demonstrates how to extend the mocked objects for a particular test.
public class PaymentRequestFormTest {

    private PaymentView paymentView;

    @Rule
    public JsfMock jsfMock = new JsfMock();

    @Before
    public void initialize() {
        paymentView = mock(PaymentView.class);
        ...
    }

    @Test
    public void toJson() {
        // Mock URL and context path
        StringBuffer requestURI = new StringBuffer("http://localhost:8080/webshop");
        Mockito.when(jsfMock.mockHttpServletRequest.getRequestURL()).thenReturn(requestURI);
        Mockito.when(jsfMock.mockHttpServletRequest.getContextPath()).thenReturn("/webshop");

        // Invoke toJson method
        String json = PaymentRequestForm.toJson(jsfMock.mockFacesContext, paymentView);

        // Verify
        ...
    }
}
Any feedbacks are welcome.

Tuesday, May 7, 2013

JSF: choice between legacy components and fashionable performance killers

This blog post was originated due to performance issues in one big web application. Everybody optimizes Java code, but it seems nobody try to optimize the JavaScript code. Strange, because on the client-side there is much room for improvements. I would state, even more than on the server-side. We will analyse the perfomance of editable JSF standard components, sometimes called "legacy", and modern PrimeFaces components with rich JavaScript widgets. This is a neutral analyse, without to blame any library or any person. Only facts.

Well. What do we want to test? The goal is to measure the client-side perfomance (without backend logic) of JS script block executions for PrimeFaces p:inputText / p:selectOneMenu. We want to test an editable p:dataTable with inputs / selects components in table cells. The table has 25 rows and 16 columns, that means 25 * 16 = 400 cells. Every cell contains either an input or a select component. There are 6 test cases. Standard h:inputText and h:selectOneMenu don't have JS script blocks, so it is interesting to see what for impact JS widgets have.


The entire test project is available on GitHub. Simple clone or download it and run with built-in Maven Jetty plugin. The page load speed is measured with the new Navigation Timing JavaScript API for accurately measuring performance on the web. The API provides a simple way to get accurate and detailed timing statistics. It's more precise than using JS Date object. The Navigation Timing JavaScript API is available in Internet Explorer 9 and higher, last versions of Google Chrome and Firefox. The code to measure the time after the current response has been arrived until the window onload event is fired is shown on the GitHub.

JavaScript is single-threaded, so let's see how sequential script block executions can slow down displaying a web page. If we only test h:inputText and p:inputText, the difference is marginal. The page load time is almost the same. Running on Windows 7 and Firefox 20.0.1 I could only see that the table with p:inputText needs ca. 200-300 ms more than the table with h:inputText. This is a good result which means the JS script execution for one p:inputText takes less than 1 ms. Really good. Congrats to PrimeFaces. A mix test with inputs and selects shown that the page with PrimeFaces components takes approximately 1.5 sek. more than the page with standard components. Adding more PrimeFaces select components slows the page rendering time down. Extreme case is to have only p:selectOneMenu components. This is a perfomance killer and the reason why our web application was too slow. Internet Explorer shows the well-known error message "A script on this page is causing Internet Explorer to run slowly". Take a look at page load time self.

Running on Windows 7 and Firefox 20.0.1

h:selectOneMenu

 

p:selectOneMenu


If we assume that component renderers take approximately the same time to render HTML output, we can calculate the time for JS script block execution of a single p:selectOneMenu. This time is 11,3 ms. That's too much. The reason may be many inefficient jQuery selectors in widget's constructor. I don't know and it doesn't matter here. On my notebook with Ubuntu, I got a huge time difference ca. 10 sek. The browser with 400 p:selectOneMenu tags almost "freezes".

Running on Kubuntu 12.04 and Firefox 20.0

h:selectOneMenu


p:selectOneMenu


Conclusion

Some people say "JSF is slow, JSF is not a right technology". Wrong. JSF is fast enough. It depends on how to use this framework. Editing of everything at once is nice, but obviously it is not recommended to use a large editable DataTable with rich JSF components. What would be an alternative for editable DataTable? There are many ways, depending on individual preferences. I will try to propose some ones.
  1. Use standard JSF select components in large editable tables. But what is with theming? No problem. All modern browser, also IE8 and higher allow to style native HTML select elements. You can adjust colors for borders, background and let selects look more or less stylish according to applied theme. Presupposed is of course, you don't need advanced features such as custom content within select components (filter functionality or whatever).
  2. Cell editing feature in PrimeFaces renders native select elements and it is fast.
  3. Row editing feature in PrimeFaces renders native select elements and it seems to be also fast.
  4. Master-Detail approach in one view. You select a row and see details to edit. Details can be shown outside of the table - on the right side or at the top, depending on table's width / height.
  5. Master-Detail approach with separate views. You select a row and switch the views. Instead of table, you see details like in the MasterDetail component of PrimeFaces Extensions. From details you can go on to another levels to create / edit more details and then, at the end, jump to the overview table again.
I hope, you enjoyed this post :-). Comments and suggestions are welcome.

Friday, December 16, 2011

Install Windows VHDs on Linux for testing websites with Internet Explorer

Need to test websites in multiple Internet Explorer versions under Linux / Mac OSX? No problem. Microsoft created free Windows Virtual PC VHDs with the purpose of allowing web designers to test websites in Internet Explorer 7-9. There is a script ievms simplifying the installation. But before you have to install the latest VirtualBox. You can do this e.g. via Package Manager (Ubuntu), Yast (OpenSuse) or via Linux terminal
 
sudo apt-get install virtualbox-4.1
 
The next step is the installation of VirtualBox Extension Pack. This is necessary for USB 2.0 devices, shared folders, better screen resolution, etc. The best way is to download and install the package for the VirtualBox Extension Pack manually.
 
wget http://download.virtualbox.org/virtualbox/4.1.6/Oracle_VM_VirtualBox_Extension_Pack-4.1.6-74713.vbox-extpack
VBoxManage extpack install Oracle_VM_VirtualBox_Extension_Pack-4.1.6-74713.vbox-extpack
 
Open VirtialBox tnen and go to File > Preferences > Extensions tab. Click there the "add" button and browse to the VirtualBox Extension Pack. Now you need to install curl (tool to transfer data from or to a server) and unrar (command-line applications for extracting RAR archives). In Ubuntu, install them using the command below.
 
sudo apt-get install curl unrar
 
To download and run the mentioned above script, use the following command in a terminal.
 
curl -s https://raw.github.com/xdissent/ievms/master/ievms.sh | bash
 
The above command will download Windows VHDs for IE7, IE8 and IE9. If you only need one specific Internet Explorer version, e.g. IE9, you can run:
 
curl -s https://raw.github.com/xdissent/ievms/master/ievms.sh | IEVMS_VERSIONS="9" bash
 
At this point, the download will start and it will take a while. The VHD archives are massive and can take hours to download. Each version is installed into a subdirectory of ~/.ievms/vhd/. At the end you can delete the downloaded archives (*.exe and *.rar files) below this folder if you want to free up some space. You are ready to test websites in IE7-IE9 now. The password for all VMs is "Password1". The picture below shows a VirtualBox with IE9.


The next picture shows that I set shared folders to exchange data between Windows 7 in VM and my Kubuntu 11.10 (Oneiric Ocelot).


You can start your web application on Linux. But how to access a running web application on Windows (VirtualBox)? Linux has a command tool ifconfig to configure, control and query TCP/IP network interface parameters. Open a terminal and type ifconfig. Find an IP address you Linux mashine is accessible from. I have e.g. 192.168.254.21 for wlan0 interface. You are able to call now
 
http://192.168.254.21:8080/...
 
in Interner Exloper on Windows. The IP address is like a localhost for your web application running on Linix. Remaning part behind this address is the same on both OS.

Sunday, May 29, 2011

Testing client-server communication with Java Scripting API

I would like to share my best practice for client- and server-side testing with Java Scripting API introduced in Java SE 6. As example I want to test simultaneous JSON serialization / deserialization on both sides. I'm going to use json2 from Douglas Crockford on client-side and Gson on server-side. I want to utilize the class Circle from my previous post and write JUnit tests for its serialization / deserialization. At first we need to implement the same interface by Java and JavaScript. It's convenient to implement an Java interface by script functions or methods. By using interfaces we can avoid having to use the javax.script API in many places.

JsonProvide.java
public interface JsonProvider
{
    public Object fromJson(String json);

    public String toJson(Object object);
}
JsonProvider is an interface which is used later to access correspondent JavaScript methods.

jsonTest.js
var jsonProvider = new Object();

// produces an JavaScript object or array from an JSON text.
jsonProvider.fromJson = function(json) {
    var obj = JSON.parse(json);
    return makeTestable(obj);
};

// produces an JSON text from an JavaScript object or array
jsonProvider.toJson = function(object) {
    var obj = makeTestable(object);
    return JSON.stringify(obj);
};

function makeTestable(obj) {
    obj.getValue = function(property) {
        return this[property];
    };

    return obj;
}

// Test object
var circle = {
    uuid: "567e6162-3b6f-4ae2-a171-2470b63dff00",
    x: 10,
    y: 20,
    movedToFront: true,
    rotationDegree: 90,
    radius: 50,
    backgroundColor: "#FF0000",
    borderColor: "#DDDDDD",
    borderWidth: 1,
    borderStyle: "-",
    backgroundOpacity: 1.0,
    borderOpacity: 0.5,
    scaleFactor: 1.2
};
There are two methods fromJson / toJson and a helper function makeTestable in order to get any value of JavaScript objects from Java. The test object in JavaScript is called circle. The corresponding Java class is called Circle and has the same fields with getter / setter. We can write an JUnit test now.
import com.google.gson.Gson;
import com.googlecode.whiteboard.model.Circle;
import org.apache.commons.beanutils.PropertyUtils;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.FileNotFoundException;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

public class JsonTest
{
    private static Gson gson;
    private static ScriptEngine engine;
    private static JsonProvider jsonProvider;

    @BeforeClass
    public static void runBeforeClass() {
        // create Gson
        GsonBuilder gsonBilder = new GsonBuilder();
        gson = gsonBilder.serializeNulls().create();

        // create a script engine manager
        ScriptEngineManager factory = new ScriptEngineManager();
        // create JavaScript engine
        engine = factory.getEngineByName("JavaScript");

        try {
            // evaluate JavaScript code from the json2 library and the test file
            engine.eval(new java.io.FileReader("src/main/webapp/resources/js/json2-min.js"));
            engine.eval(new java.io.FileReader("src/test/resources/js/jsonTest.js"));

            // get an implementation instance of the interface JsonProvider from the JavaScript engine,
            // all interface's methods are implemented by script methods of JavaScript object jsonProvider
            Invocable inv = (Invocable) engine;
            jsonProvider = inv.getInterface(engine.get("jsonProvider"), JsonProvider.class);
        } catch (ScriptException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    @AfterClass
    public static void runAfterClass() {
        gson = null;
        jsonProvider = null;
    }

    @Test
    public void JavaScript2Java() {
        // get JavaScript object
        Object circle1 = engine.get("circle");

        // client-side: make JSON text from JavaScript object
        String json = jsonProvider.toJson(circle1);

        // server-side: convert JSON text to Java object
        Circle circle2 = gson.fromJson(json, Circle.class);

        // compare two objects
        testEquivalence(circle2, circle1);
    }

    @Test
    public void Java2JavaScript() {
        // create Java object
        Circle circle1 = new Circle();
        circle1.setUuid(UUID.randomUUID().toString());
        circle1.setX(100);
        circle1.setY(100);
        circle1.setMovedToFront(false);
        circle1.setRotationDegree(0);
        circle1.setRadius(250);
        circle1.setBackgroundColor("#FFFFFF");
        circle1.setBorderColor("#000000");
        circle1.setBorderWidth(3);
        circle1.setBorderStyle(".");
        circle1.setBackgroundOpacity(0.2);
        circle1.setBorderOpacity(0.8);
        circle1.setScaleFactor(1.0);

        // server-side: convert Java object to JSON text
        String json = gson.toJson(circle1);

        // client-side: make JavaScript object from JSON text
        Object circle2 = jsonProvider.fromJson(json);

        // compare two objects
        testEquivalence(circle1, circle2);
    }

    @SuppressWarnings("unchecked")
    private void testEquivalence(Object obj1, Object obj2) {
        try {
            Map<String, Object> map = PropertyUtils.describe(obj1);
            Set<String> fields = map.keySet();
            Invocable inv = (Invocable) engine;

            for (String key : fields) {
                Object value1 = map.get(key);
                if (!key.equals("class")) {
                    Object value2 = inv.invokeMethod(obj2, "getValue", key);
                    if (value1 instanceof Number && !(value1 instanceof Double)) {
                        // JS number is always converted to Java double ==> only doubles can be compared,
                        // see http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html
                        value1 = new Double(value1.toString());
                    }

                    Assert.assertEquals("Value of property '" + key + "' was wrong converted", value2, value1);
                }
            }
        } catch (Exception e) {
            throw new IllegalStateException("Equivalence test of two objects failed!", e);
        }
    }
}
I create a Gson and a ScriptEngine instances in the method runBeforeClass() and load all needed scripts into the ScriptEngine. After that I get an implementation instance of the interface JsonProvider from the JavaScript engine. Now I'm able to call JavaScript methods from my JsonProvider implementation. There are two tests:

@Test public void JavaScript2Java()

I test here the use case if an JavaScript object (circle) gets converted to an JSON text, sent to the server and converted there to an Java object (Circle). The original JavaScript object and the result Java object are compared afterwards.

@Test public void Java2JavaScript()

I test here the use case if a created Java object (Circle) gets converted to an JSON text, sent to the client and converted there to an JavaScript object (circle). The objects are compared to ensure their equivalence.

You can also use "JavaScript to Java Communication" with Java Scripting API and access Java classes, objects and methods from JavaScript. Pretty cool.

Wednesday, October 27, 2010

Tests with PowerMock and MyFaces Test frameworks

In one of projects we use Mockito as test framework. It's a great framework having some limitations. We also use different mock objects for JSF environment which come from various sources. A not comfortable mishmash. I have decided to introduce PowerMock and MyFaces Test frameworks. This changing was very effective. First I want to compare two test approaches:
  • Old approach: Utilization of Mockito and JSF / Servlet mocks from "dead" Apache Shale, Spring, ebtam FacesContext.
  • Disadvantages: Limitations, no mocking of static, final, private classes / methods. Mix of different mocks causes an unconsistent environment. Hacks for the production code.
  • New approach: Utilization of PowerMock and MyFaces Test frameworks.
  • Advantages: Mocking of static, final, private classes / methods and more is possible. Unified mock objects and consistent weaving for all fundamental JSF / Servlet pendants.
PowerMock extends Mockito by using a custom classloader and bytecode manipulation. It enables mocking of static methods, constructors, final classes and methods, private methods and more. MyFaces Test Framework provides mock object libraries and base classes for creating own test cases. Mocks for the following APIs are provided:
  • JavaServer Faces
  • Servlet
The mock netting is as real. Created FacesContext instance will have been registered in the appropriate thread local variable, to simulate what a servlet container would do. The following section shows how to write power JSF 2 tests. For easing use of the new approach there is a class JsfServletMock which provides an access to JSF 2 and Servlet mocks.
package com.xyz.webapp.basemock;

import org.apache.myfaces.test.base.junit4.AbstractJsfTestCase;
import org.apache.myfaces.test.mock.*;
import org.apache.myfaces.test.mock.lifecycle.MockLifecycle;

public class JsfServletMock extends AbstractJsfTestCase
{
    public MockApplication20 mockApplication;
    public MockExternalContext20 mockExternalContext;
    public MockFacesContext20 mockFacesContext;
    public MockLifecycle mockLifecycle;
    public MockServletContext mockServletContext;
    public MockServletConfig mockServletConfig;
    public MockHttpSession mockHttpSession;
    public MockHttpServletRequest mockHttpServletRequest;
    public MockHttpServletResponse mockHttpServletResponse;

    public JsfServletMock() {
        try {
            super.setUp();
        } catch (Exception e) {
            throw new IllegalStateException("JSF / Servlet mocks could not be initialized");
        }

        // JSF API
        mockApplication = (MockApplication20) application;
        mockExternalContext = (MockExternalContext20) externalContext;
        mockFacesContext = (MockFacesContext20) facesContext;
        mockLifecycle = lifecycle;

        // Servlet API
        mockServletContext = servletContext;
        mockServletConfig = config;
        mockHttpSession = session;
        mockHttpServletRequest = request;
        mockHttpServletResponse = response;
    }
}
An object of this class can be used in tests per delegation pattern. Define the PowerMockRunner with @RunWith at the beginning. Define classes which static, final, private methods will be mocked / tested with @PrepareForTest. Take an example.
import com.xyz.webapp.basemock.JsfServletMock;
import com.xyz.webapp.util.FacesUtils;
import com.xyz.webapp.util.Observer;
import com.xyz.webapp.util.ObserverUtil;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;
import static org.powermock.api.mockito.PowerMockito.*;

@RunWith(PowerMockRunner.class)
@PrepareForTest({FacesUtils.class, ObserverUtil.class})
public class DataExportWorkflowPageTest
{
    private JsfServletMock jsfServletMock;

    @Mock
    private StatusProfileFinder statusProfileFinder;

    @Mock
    private Collection<Observer> observers;

    private DataExportWorkflowPage dewp = new DataExportWorkflowPage();

    @Before
    public void before() {
        jsfServletMock = new JsfServletMock();

        dewp.setObservers(observers);
        dewp.setStatusProfileFinder(statusProfileFinder);
    }

    @Test
    public void initialize() {
        mockStatic(FacesUtils.class);

        DataExportHistoryForm dehf = mock(DataExportHistoryForm.class);
        DataExportContentForm decf = mock(DataExportContentForm.class);

        WorkflowData workflowData = mock(WorkflowData.class);

        when(FacesUtils.accessManagedBean("dataExportHistoryForm")).thenReturn(dehf);
        when(FacesUtils.accessManagedBean("dataExportContentForm")).thenReturn(decf);

        List<StatusProfile> spList = new ArrayList<StatusProfile>();
        StatusProfile sp1 = new StatusProfile(GenericObjectType.DOCUMENT);
        sp1.setBusinessKey("document");
        spList.add(sp1);
        StatusProfile sp2 = new StatusProfile(GenericObjectType.ACTIVITY);
        sp2.setBusinessKey("activity");
        spList.add(sp2);
        StatusProfile sp3 = new StatusProfile(GenericObjectType.EXPENSE_DOCUMENT_ITEM);
        sp3.setBusinessKey("expense");
        spList.add(sp3);

        jsfServletMock.mockHttpSession.setAttribute(Constants.SESSION_CLIENT, new Client());

        when(statusProfileFinder.getAllLimitBy(any(Client.class), eq(Constants.MAX_RESULTS_DEFAULT))).thenReturn(spList);
        when(FacesUtils.accessManagedBean("workflowData")).thenReturn(workflowData);

        dewp.initialize();

        verify(observers).add(dehf);
        verify(observers).add(decf);
        verifyNoMoreInteractions(observers);

        assertEquals(GenericObjectType.DOCUMENT.getId(), dewp.getStatusProfile());
        assertEquals(workflowData, dewp.getWorkflowData());
    }

    ...
}
After JsfServletMock instantiation in a method annotated with @Before we have a full access to mock objects. E.g. MockHttpSession can be accessed by jsfServletMock.mockHttpSession. In the example I want to mock the static method FacesUtils.accessManagedBean(String) in the static class. We have to write first
mockStatic(FacesUtils.class);
to achieve that. And then quite normally
when(FacesUtils.accessManagedBean(...)).thenReturn(...);
In another small example I use the call
verifyStatic();
to verify if the subsequent static call got really called. In the example below we verify if
ObserverUtil.notifyAll(depd);
was called within
depd.setProcedureStep2(null);
@RunWith(PowerMockRunner.class)
@PrepareForTest(ObserverUtil.class)
public class DataExportProcedureDataTest
{
    private DataExportProcedureData depd;

    @Mock
    private Collection<Observer> observers;

    @Before
    public void before() {
        depd = new DataExportProcedureData();
        depd.setObservers(observers);
    }

    @Test
    public void setProcedureStep2() {
        mockStatic(ObserverUtil.class);

        depd.setProcedureStep2(null);

        verifyStatic();
        ObserverUtil.notifyAll(depd);
    }

    ...
}
You see we can do both - mock static methods and verify behaviors.

Bring power to JSF tests! No need for workarounds any more! Keep your production code clean from the test driven stuff!