The maven archetype for the wicket quickstart app doesn't include dependencies for JUnit or EasyMock sjavascript:void(0)o I had to add those to the POM....
<dependency>
<groupId>easymock</groupId>
<artifactId>easymock</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.4</version>
</dependency>
Then I got dumb for a few minutes and thought "how the heck do you sync up the IDEA project with the POM?".... Well, there's a sync button in the Maven Projects tool in IDEA. Click. Zing. Done. Nice. I haven't set the project to automatically download javadocs and sources so I had to click that button too. ... Why haven't I made it automatic? Good question. I'll do that now...
Once I had the dependencies in place I started writing a test. Ther are a couple of examples online that I'm using for reference. This one here is actually about testing wicket apps that use Spring. I might use Spring so it's interesting to see that option. Also, it uses JUnit and EasyMock which I do want now. Gotcha: this example uses some deprecated EasyMock stuff.
The other page about wicket testing just covers using WicketTester on its own.
I decided to TDD a user detail page. It's really dumb by design to start with and only requires that a name be displayed. (I guess I'm doing incremental design here.) To make it a little interesting I made an interface for a
UserStore (think DAO if you must, think REPOSITORY if you know DDD) with just one method...
public interface UserStore {
public List getAllUsers();
}
Caveat: I went on a little exploration here to figure out how to do the next part. I actually goofed and made the test second, to be honest. After batting it around a little I got it to work.
Here's my user detail page test...
public class UserDetailPageTest {
private UserStore mock;
private WicketTester wicketTester;
@Before
public void setUp() throws Exception {
mock = createMock(UserStore.class);
wicketTester = new WicketTester();
}
@Test
public void testRender() throws Exception {
List users = new ArrayList();
users.add("joe");
users.add("bob");
users.add("ted");
mock.getAllUsers();
expectLastCall().andReturn(users);
replay(mock);
wicketTester.startPage(new ITestPageSource() {
public Page getTestPage() {
return new UserDetailPage(mock);
}
});
wicketTester.assertRenderedPage(UserDetailPage.class);
wicketTester.assertLabel("userName", "joe");
verify(mock);
}
}
Yeah. Pretty lame. I mean, why the heck does "joe" just show up. But the point is that it got me to figure out how to do it. One thing that got me was that I tried to inject the UserStore with a setter but there seemed to be no step allowing me to call the setter. So I inject it in the constructor and use ITestPageSource. Supposedly Spring injection can help so I'll have to try that out (later).
My page looks like this (java, then html)...
public class UserDetailPage extends WebPage {
private UserStore userStore;
public UserDetailPage(UserStore userStore) {
this.userStore = userStore;
add(new Label("userName", userStore.getAllUsers().get(0)));
}
}
---------------------
<html xmlns:wicket="http://wicket.sourceforge.net/">
<head>
<title>User Detail</title>
</head>
<body>
<h2>User Detail</h2>
<span wicket:id="userName">[user name will go here]</span>
</body>
</html>
Yippee. I learned some stuff today. Now I'm adding a link from the quickstart's HomePage to my UserDetailPage with TDD...
I already made a HomePageTest class so I just added a test method to it...
@Test
public void testUserDetailLink() throws Exception {
wicketTester.startPage(HomePage.class);
wicketTester.assertRenderedPage(HomePage.class);
wicketTester.assertPageLink("linkToUserDetail", UserDetailPage.class);
}
Which dutifully failed with
org.apache.wicket.WicketRuntimeException: path: 'linkToUserDetail' does not exist for page: HomePageSo I add it.
<html 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>
<br/>
<a href="#" wicket:id="linkToUserDetail">[go to user detail]</a>
</body>
</html>
But then the test fails because Wicket instantiates the UserDetailsPage with reflection but there's no empty constructor. So I add that. But that means I have to set the UserStore to something in the empty constructor.... So I decide to set it to null and add a bit of smarts to avoid an NPE...
public class UserDetailPage extends WebPage {
private UserStore userStore;
public UserDetailPage() {
this(null);
}
public UserDetailPage(UserStore userStore) {
this.userStore = userStore;
String userName = (userStore!= null && !userStore.getAllUsers().isEmpty()) ?
userStore.getAllUsers().get(0) : "No User Found";
add(new Label("userName", userStore.getAllUsers().get(0)));
}
}
And that almost works but ... EasyMock complains because it only expects on call to the UserStore mock but now there are two. So I tell it to expect any number of calls like so (in UserDetailPageTest)...
mock.getAllUsers();
expectLastCall().andReturn(users).anyTimes();
replay(mock);
And it works!!
OK. I learned some stuff today! I dropped the ball a bit and didn't really do test-first when starting the user detail page development. But I did do it for the link and it lead to interesting stuff. Also, using TDD has really got me a better understanding of Wicket.
Weird thing is. I am totally confident the page and link will work even though I never deployed the application. ... Hmmmm. "Totally confident" I better check that.
Launch Jetty from in IDEA and...
HA! It failed. I got an NPE on the UserDetailPage because I never tested how it rendered if using the empty constructor. Back in a minute....
So I made this test in UserDetailPageTest, letting wicket use reflection on the empty constructor...
@Test
public void testRenderWithoutUsers() throws Exception {
wicketTester.startPage(UserDetailPage.class);
wicketTester.assertRenderedPage(UserDetailPage.class);
wicketTester.assertLabel("userName", "No User Found");
}
It of course fails. So I follow the stacktrace and discover that I forgot to use the reults of the nifty avoid-the-NPE code I put in. Silly me. Actually, IDEA was giving me warnings too. More silly me.
So this....
public UserDetailPage(UserStore userStore) {
this.userStore = userStore;
String userName = (userStore!= null && !userStore.getAllUsers().isEmpty()) ?
userStore.getAllUsers().get(0) : "No User Found";
add(new Label("userName", userStore.getAllUsers().get(0)));
}
Becomes this...
public UserDetailPage(UserStore userStore) {
this.userStore = userStore;
String userName = (userStore!= null && !userStore.getAllUsers().isEmpty()) ?
userStore.getAllUsers().get(0) : "No User Found";
add(new Label("userName", userName));
}
And the tests all pass, and the web app runs in Jetty as expected.
So. for one last time I'll say it... Hey! I learned some stuff today!
This is the best TDD experience I've ever had with a web application. Thank you Wicketeers!

1 comment:
tdd in wicket sounds great, the only thing which is strange to me is the declaration of the dao stuff there. I think in tdd you would start with constructor which takes String parameter as user name, you would get rid of all the mocking stuff which I don't like at all :) btw nice article thnx :)
Post a Comment