Testing AJAX

For example, the page below contains an AJAX link that will update the current number in the page:

<html>
<body>
        <a wicket:id="calcNext">Calculate next</a>
        <span id="output">Current: <span wicket:id="current">0</span></span>.
</body> 
</html>

public interface CalcService {
        int calcNext(int current);
}

public class AjaxPage extends WebPage {
        @SpringBean
        private CalcService service;
        private int current = 0;
        private Label currentLabel;

        public AjaxPage() {
                AjaxLink<Void> calcNext = new AjaxLink<Void>("calcNext") {

                        @Override
                        public void onClick(AjaxRequestTarget target) {
                                current = service.calcNext(current);
                                target.addComponent(currentLabel);
                        }
                };
                add(calcNext);
                currentLabel = new Label("current", new PropertyModel<Integer>(this,
                                "current"));
                add(currentLabel);
                currentLabel.setOutputMarkupId(true);
        }
}

To test it, as usual, you will mock the service. Here, you just increment the number. To test the AJAX effect, you wait until the AJAX processing is completed and then check the result:

@Test
public class AjaxPageTest {
        public void testEasyWaitingForAjax() {
                MockableSpringBeanInjector.mockBean("service", new CalcService() {

                        public int calcNext(int current) {
                                return current + 1;
                        }
                });
                DefaultSelenium selenium = WebPageTestContext.getSelenium();
                WicketSelenium ws = new WicketSelenium(selenium);
                ws.openBookmarkablePage(AjaxPage.class);
                assert selenium.getText("output").equals("Current: 0");
                selenium.click("link=Calculate next");
                ws.waitUntilAjaxDone();
                assert selenium.getText("output").equals("Current: 1");
                selenium.click("link=Calculate next");
                ws.waitUntilAjaxDone();
                assert selenium.getText("output").equals("Current: 2");
        }

}

Testing a page that requires a logged in user

For example, the page below checks if a user has logged by querying the session:

<html>
Hello: <span wicket:id="name" id="name">John</span>.
</html>

public class PageRequiringLogin extends WebPage {

        public PageRequiringLogin() {
                MySession session = (MySession) WebSession.get();
                String currentUser = session.getCurrentUser();
                if (currentUser == null) {
                        throw new RestartResponseAtInterceptPageException(LoginPage.class);
                }
                add(new Label("name", currentUser));
        }

}

You'd like your test code to simulate both cases: a user has logged and no one has logged in. To simulate the former, let the session ask the application to see if there is a mocked user before really looking for a real user:

public class MySession extends WebSession {
        private String currentUser;
        ...
        public String getCurrentUser() {
                MyApp app = (MyApp) WebApplication.get();
                String mockedUser = app.getMockedUser();
                if (mockedUser != null) {
                        return mockedUser;
                }
                return currentUser;
        }
}

Then your test case can get access to the application and then put a mocked user there:

@Test
public class PageRequiringLoginTest {
        @BeforeMethod
        public void mockLogin() {
                MyApp app = (MyApp) WebPageTestContext.getWebApplication();
                app.setMockedUser("u1");
        }

        @AfterMethod
        public void mockLogout() {
                MyApp app = (MyApp) WebPageTestContext.getWebApplication();
                app.setMockedUser(null);
        }

        public void testAlreadyLoggedIn() {
                DefaultSelenium selenium = WebPageTestContext.getSelenium();
                WicketSelenium ws = new WicketSelenium(selenium);
                ws.openBookmarkablePage(PageRequiringLogin.class);
                assert selenium.isTextPresent("u1");
        }
}

To test a case where the user hasn't logged in (so a login page is displayed), just call mockLogout() first:

@Test
public class PageRequiringLoginTest {
        @BeforeMethod
        public void mockLogin() {
                ...
        }

        @AfterMethod
        public void mockLogout() {
                ...
        }
        public void testNeedToLogIn() {
                mockLogout();
                DefaultSelenium selenium = WebPageTestContext.getSelenium();
                WicketSelenium ws = new WicketSelenium(selenium);
                ws.openBookmarkablePage(PageRequiringLogin.class);
                assert selenium.isTextPresent("Please login");
        }
}

Invoking a Wicket page whose constructor takes arguments

Sometimes your Wicket page may take arguments. For example, a page that takes and displays some calculation result, or a listing page that takes a search condition. Let's say the page is like:

<html>
Hello: <span wicket:id="name" id="name">John</span>.
</html>

public class NonBookmarkablePage extends WebPage {
        public NonBookmarkablePage(SomeObj obj) {
                add(new Label("name", obj.getName()));
        }
}

To invoke it in your test case, just specify the page class and the argument values:

@Test
public class NonBookmarkablePageTest {
        public void testDisplay() {
                DefaultSelenium selenium = WebPageTestContext.getSelenium();
                WicketSelenium ws = new WicketSelenium(selenium);
                SomeObj obj = new SomeObj("xyz");
                ws.openNonBookmarkablePage(NonBookmarkablePage.class, obj);
                assert selenium.getText("name").equals("xyz");
        }
}

Checking if a Wicket page is passing the correct data to the next page

Sometimes you have a Wicket page that passes some data to the next page, e.g., this page gets a product ID from the user and the next page lets the user edit the product data. To test the first page, you may create the product data page and check if it is displaying the right data. But this delays the testing of the first work significantly. To test it immediately, you can simply check if it's getting and passing the correct product ID. To do that, use an extra bean to perform page navigation:

<html>
        <form wicket:id="form">
                <input type="text" wicket:id="productID">
                <input type="submit" value="OK">
        </form>
</html>

public class ProductIDPage extends WebPage {
        private String productID;
        @SpringBean
        private PageFlowNavigator navigator;

        public ProductIDPage() {
                Form<ProductIDPage> form = new Form<ProductIDPage>(
                                "form",
                                new CompoundPropertyModel<ProductIDPage>(this)) {
                        protected void onSubmit() {
                                ProductDetailsPage r = new ProductDetailsPage(productID);
                                navigator.setResponsePage(this, r);
                        }
                };
                add(form);
                form.add(new TextField<String>("productID"));
        }
}

To check the product ID, mock the page flow navigator in your test case:

@Test
public class ProductIDPageTest {
        private String log;

        public void testProvidingCorrectProductID() {
                MockableSpringBeanInjector.mockBean("navigator",
                                new PageFlowNavigator() {
                                        public void setResponsePage(Component from, Page to) {
                                                ProductDetailsPage r = (ProductDetailsPage) to;
                                                log = r.getProductID();
                                                // don't really display the next page which you probably
                                                // haven't worked on yet. Just redisplay the current
                                                // page.
                                        }
                                });
                DefaultSelenium selenium = WebPageTestContext.getSelenium();
                WicketSelenium ws = new WicketSelenium(selenium);
                ws.openBookmarkablePage(ProductIDPage.class);
                selenium.type("productID", "p123");
                selenium.click("//input[@type='submit']");
                selenium.waitForPageToLoad("3000");
                assert log.equals("p123");
        }
}

To really display the product details page in production, you need to provide a real page flow navigator. One such class is provided: DefaultPageFlowNavigator. Make sure you create a bean from this class. For example, in your Spring XML file:

<beans>
    ...
        <bean class="com.ttdev.wicketpagetest.DefaultPageFlowNavigator">
        </bean>
</beans>

Overriding web.xml (or overriding non-service Spring beans)

Your application may uses Spring beans to, say, access an LDAP server. But when unit testing your pages, it is not needed and should be disabled. To do that, you can provide an additional Spring context XML file to override those beans. So, create an context-test.xml file in the root of the classpath:

<beans ...>
        <!-- Disable the LDAP by making it a simple String -->
        <bean id="ldapServer" class="java.lang.String"/>
        ...
</beans>

How to tell Spring to load this context-test.xml file in addition to the normal context.xml file? In principle, you need to contextConfigLocation parameter in web.xml:

<web-app ...>
        ...
        <context-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:/context.xml classpath:/context-test.xml</param-value>
        </context-param>
</web-app>

However, you definitely shouldn't modify web.xml as it is to be used in production. The idea is to create an overriding web.xml file. So, create a web-test.xml file in src/test/resources:

<web-app ...>
        <context-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:/context.xml classpath:/context-test.xml</param-value>
        </context-param>
</web-app>

To tell Jetty to apply this overriding web.xml file (web-test.xml) when launching your web application, create a class to wrap around the WebPageTestContext class. This class will specify the overriding web.xml file:

public class TestFixture {
        @BeforeSuite
        public void setUp() throws Exception {
                Configuration cfg = new Configuration();
                cfg.setOverrideWebXml("web-test.xml");
                WebPageTestContext.beforePageTests(cfg);
        }
        @AfterSuite
        public void tearDown() throws Exception {
                WebPageTestContext.afterPageTests();
        }
}

In the testng.xml file, run this TestFixture instead of the WebPageTestContext:

<suite name="wicket-page-test-sample">
        <test verbose="2" name="tests" annotations="JDK">
                <packages>
                        <package name="..."></package>
                </packages>
                <classes>
                        <class name="com.foo.TextFixture"></class>
                </classes>
        </test>
</suite>

Testing a palette

For example, the page below contains a palette component to let the user choose which products to delete:

<html>
<body>
        <form wicket:id="f">
                <span wicket:id="palette"/>
                <input type="submit" value="OK">
        </form>
</body> 
</html>

public class PalettePage extends WebPage {
        @SpringBean
        private ProductService ps;
        private List<Product> selectedProducts;

        @SuppressWarnings("unchecked")
        public PalettePage() {
                Form<Void> f = new Form<Void>("f") {
                        private static final long serialVersionUID = 1L;

                        @Override
                        protected void onSubmit() {
                                for (Product p : selectedProducts) {
                                        ps.delete(p);
                                }
                        }
                };
                add(f);
                selectedProducts = new ArrayList<Product>();
                List<Product> availableProducts = ps.getAll();
                Palette<Product> palette = new Palette<Product>("palette", new Model(
                                (Serializable) selectedProducts), new Model(
                                (Serializable) availableProducts), new ChoiceRenderer<Product>(
                                "name", "id"), 3, true);
                f.add(palette);
        }
}

To test it, as usual, you will mock the service to provide the list of products and to check the product(s) deleted. See the code below to see how to select an option (a product), how to click the add button and how to inspect the options listed in the two <select> elements:

@Test
public class PalettePageTest {
        private Product deleted;

        public void testPalette() {
                MockableSpringBeanInjector.mockBean("ps", new ProductService() {

                        public List<Product> getAll() {
                                List<Product> products = new ArrayList<Product>();
                                products.add(new Product(0L, "ball pen"));
                                products.add(new Product(1L, "eraser"));
                                products.add(new Product(2L, "paper clip"));
                                return products;
                        }

                        public void delete(Product p) {
                                deleted = p;
                        }

                        public void add(Product p) {
                        }
                });
                DefaultSelenium selenium = WebPageTestContext.getSelenium();
                WicketSelenium ws = new WicketSelenium(selenium);
                ws.openBookmarkablePage(PalettePage.class);
                String[] allProducts = selenium.getSelectOptions("wicket=//choices");
                assert allProducts.length == 3;
                assert allProducts[0].equals("ball pen");
                assert allProducts[1].equals("eraser");
                assert allProducts[2].equals("paper clip");
                selenium.select("wicket=//choices", "eraser");
                selenium.click("wicket=//addButton");
                allProducts = selenium.getSelectOptions("wicket=//choices");
                assert allProducts.length == 2;
                assert allProducts[0].equals("ball pen");
                assert allProducts[1].equals("paper clip");
                String[] selectedProducts = selenium.getSelectOptions("wicket=//selection");
                assert selectedProducts.length == 1;
                assert selectedProducts[0].equals("eraser");
                selenium.click("//input[@value='OK']");
                selenium.waitForPageToLoad("3000");
                assert deleted.getId() == 1L;
                assert deleted.getName().equals("eraser");
        }

}