Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

Excerpt
hiddentrue

How to achieve cleaner code and compile-time type-safety for your tests, probably saving 60% of your time


If you use Test Driven Development in Wicket, the first approach is often to use component's path names (e.g., page:panel:form:component) to test them. This is good bearable at first, but as your pages get more and more complicated and have deeper hierarhices, you will find yourself spending a whole lot of time debugging typos and misconceptions (in my opinnion: you would waste 60% of your time doing this). The solution is: make your component hierarcy type-safe so that you can GET all of your components when testing:

Code Block
public class TestHomePage extends TestCase {
	private WicketTester tester;

	@Override
	public void setUp() {
		tester = new WicketTester(new WicketApplication());
	}

	/**
	 * 
	 */
	public void testBookList() {
		// start and render the test page
		tester.startPage(HomePage.class);
		// assert rendered page class
		tester.assertRenderedPage(HomePage.class);
		// assert rendered label component
		tester.assertLabel("message",
				"If you see this message wicket is properly configured and running");

		// assert rendered page class
		HomePage homePage = (HomePage) tester.getLastRenderedPage();
		BookForm bookForm = homePage.getForm();
		BookListView bookListView = bookForm.getBookListView();
		List<BookListItem> bookItems = toLinkedList(bookListView.iterator());
		{
			// Test values of 1st book
			BookListItem bookListItem = bookItems.get(0); // Note: no hassle with
																										// run-time component path
																										// id's
			assertEquals("first", bookListItem.getNameField().getValue());
			assertEquals("", bookListItem.getAuthorField().getValue());
			assertEquals("-1", bookListItem.getTypeChoice().getValue());
		}
		{
			// Test values of 2nd book
			BookListItem bookListItem = bookItems.get(1); // Note: no hassle with
																										// run-time component path
																										// id's
			assertEquals("second", bookListItem.getNameField().getValue());
			assertEquals("", bookListItem.getAuthorField().getValue());
			assertEquals("-1", bookListItem.getTypeChoice().getValue());
		}
		{
			// Test values of 3rd book
			BookListItem bookListItem = bookItems.get(2); // Note: no hassle with
																										// run-time component path
																										// id's
			assertEquals("third", bookListItem.getNameField().getValue());
			assertEquals("", bookListItem.getAuthorField().getValue());
			assertEquals("-1", bookListItem.getTypeChoice().getValue());
		}

		{
			// Set new values
			FormTester formTester = tester.newFormTester(bookForm
					.getPageRelativePath());

			// formTester.setValue(bookItems.get(1).getAuthorField(), "Hemingway"); //
			// TODO New feature request
			tester.getServletRequest().setParameter(
					bookItems.get(1).getAuthorField().getInputName(), "Hemingway");

			// Submit changes
			// formTester.submit(bookForm.getSubmitButton()); // TODO New feature
			// request
			formTester.submit();
		}

		tester.assertRenderedPage(HomePage.class);
		// Verify the submitted value
		{
			// Test values of 2nd book
			BookListItem bookListItem = bookItems.get(1); // Note: no hassle with
																										// run-time component path
																										// id's
			assertEquals("second", bookListItem.getNameField().getValue());
			assertEquals("Hemingway", bookListItem.getAuthorField().getValue());
			assertEquals("-1", bookListItem.getTypeChoice().getValue());
		}
	}

	/**
	 * @param <T>
	 * @param iterator
	 * @return List<T>
	 */
	public static <T> List<T> toLinkedList(Iterator<T> iterator) {
		List<T> linkedList = new LinkedList<T>();
		for (; iterator.hasNext();) {
			linkedList.add(iterator.next());
		}

		return linkedList;
	}
}

...

Code Block
<html xmlns="http://www.w3.org/1999/xhtml" 
  xmlns:wicket="http://wicket.sourceforge.net">
    <head>
        <title>Wicket Quickstart Archetype Homepage</title>
    </head>
    <body>
        <strong>Wicket Quickstart Archetype Homepage</strong>
        <br/><br/>
        <span wicket:id="message">message will be here</span>
        
        <form wicket:id="form">
          <table border="1">
            <tr wicket:id="book_list">
              <th>Name:</th><td><input type="text" wicket:id="name"/></td>
              <th>Author:</th><td><input type="text" wicket:id="author"/></td>
              <th>Type:</th><td><select wicket:id="type"></select></td>
            </tr>
          </table>
          <input type="submit" wicket:id="submit" value="Submit"/>
        </form>
        
    </body>
</html>

The benefits of the introduced approach are:

  1. Compile-time type-safety
  2. By not hardcoding the runtime component paths into the tests, you can make your tests truly modular!
  3. You save 60% of your time (the time you would have wasted banging your head to the wall of wrong paths and components)
  4. The code is cleaner and nicer, easier to maintain
  5. The test-code is easier to read and makes much more sense as a spec