Posts Tagged ‘web’

Testing Wicket ListViews using IVisitor

2009-11-05

The standard solution for unit testing Wicket apps is WicketTester. It works fine, but doesn’t offer much for testing ListViews. The assertListView method lets you test the contents of the ListView, but not the UI.

You can always extract the ListView rows into a Panel, of course. But when you don’t want to do that for whatever reason, it’s simple to side-step WicketTester and do the necessary tests yourself, especially since rows that are not in a panel of their own should be pretty simple.

An approach that works well for me is visiting the ListItems using MarkupContainer.visitChildren() and then accessing their components via Component.get(). To focus on the essential part, I’m using a method listView() in my example that gets the ListView from the rendered page. Once we have it, we visit its items.

listView().visitChildren(ListItem.class, new IVisitor<ListItem>()
{
	public Object component(ListItem item)
	{
		Component uri = item.get("uri");
		assertNotNull(uri);
		assertTrue(uri.getClass().isAssignableFrom(ExternalLink.class));
		
		// etc.
		
		// If testing one row is enough, we can stop here.
		return IVisitor.STOP_TRAVERSAL;
	}
});

Once you see it, it’s trivial. WicketTester’s assertComponent() isn’t magic, you can do the same in three lines: get the Component via its path, assert it’s actually present, and assert it’s the right type.

If you want to do further tests on the component’s internals, I find it practical to expose those through package-scoped methods, provided your tests are in the same package than your app.

Finally, something that bit me once: how ListItems work with the list model. Say we have this (just a rough sketch):

class Foo
{
	List bars;
	List<Bar> getBars() { return bars; }
}

// On some page, the model is a CompoundPropertyModel&lt;Foo&gt;.
add(new ListView<Bar>("bars")
{
	protected void populateItem(final ListItem<Bar> item)
	{
		item.add(new WebMarkupContainer("id"));
	}
});

If you get hold of the WebMarkupContainer(“id”), for instance using an IVisitor like I showed above, and you call getDefaultModelObject() on it, what do you get? One might expect a Bar; after all, it’s a ListView<Bar>. Not so – you get a Foo.

ListViews work on the original List instead of a copy. Each ListItem is backed by a ListItemModel, which knows the index of the element it represents, but has no model itself. When the model is looked up, the normal Wicket model chaining happens, so we get to the CompoundPropertyModel. The ListView code of course knows about this and uses the ListItemModel’s index to get the right Bar from the list. But getDefaultModelObject() doesn’t know about this and simply returns the Foo.

If you are in that situation, explicitly ask for the ListItemModel:

((ListItemModel) component.getDefaultModel()).getObject()

should give you a Bar. Of course, if you visit the ListItem itself like in the example above instead of a nested component, you can just call getObject() on it and it does the right thing.

Advertisements

Wicket CheapoModel

2009-08-04

Sometimes you just want a very simple Model in Wicket, where you can put stuff in and get it out later via a simple property expression or a method call. For a sign-in form, for instance, you’d have two text fields for user name and password, and in onSubmit() you’d get their values and authenticate with them.

In Jonathan Locke‘s simple sign-in example of the Wicket 1.3 examples, he creates a ValueMap and uses that in a PropertyModel with the simple “username” and “password” expressions:

// El-cheapo model for form
private final ValueMap properties = new ValueMap();

// Let Components set values via a PropertyModel.
add(new TextField("username", new PropertyModel(properties, "username")));

// Get those values, e.g. in onSubmit().
session.authenticate(properties.getString("username"));

That works fine and is simple enough. It does require a bit of boilerplate code, though, and repetition of the property expression Strings in the code. Factoring them out into constants would work, but still be ugly.

The simple CheapoModel class below hides these internals and makes the code shorter and easier to read. It also provides a getter for the property expression itself, as it’s convenient and improves readability to use it as component id: one literal less. Use CheapoModel like this:

private final CheapoModel uri = new CheapoModel("uri");

// Use its PropertyModel and the property expression when building components.
add(new TextField(uri.id(), uri.model());

// Retrieve the value after the user has interacted with the component.
final String newUri = uri.value();

You can also pass in the ValueMap if you want to share one.

Here it is:
(more…)