001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.wicket.util.tester;
018:
019: import java.util.ArrayList;
020: import java.util.Arrays;
021: import java.util.Iterator;
022: import java.util.List;
023:
024: import javax.servlet.http.HttpServletResponse;
025:
026: import junit.framework.Assert;
027: import junit.framework.AssertionFailedError;
028:
029: import org.apache.wicket.Component;
030: import org.apache.wicket.Page;
031: import org.apache.wicket.ajax.AjaxRequestTarget;
032: import org.apache.wicket.feedback.FeedbackMessage;
033: import org.apache.wicket.markup.html.basic.Label;
034: import org.apache.wicket.markup.html.list.ListView;
035: import org.apache.wicket.protocol.http.HttpSessionStore;
036: import org.apache.wicket.protocol.http.MockHttpServletResponse;
037: import org.apache.wicket.protocol.http.SecondLevelCacheSessionStore;
038: import org.apache.wicket.protocol.http.UnitTestSettings;
039: import org.apache.wicket.protocol.http.WebApplication;
040: import org.apache.wicket.protocol.http.WebResponse;
041: import org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore;
042: import org.apache.wicket.session.ISessionStore;
043: import org.apache.wicket.util.diff.DiffUtil;
044: import org.slf4j.Logger;
045: import org.slf4j.LoggerFactory;
046:
047: /**
048: * A helper to ease unit testing of Wicket applications without the need for a
049: * servlet container. To start a test, either use startPage() or startPanel():
050: *
051: * <pre>
052: * // production page
053: * public class MyPage extends WebPage
054: * {
055: * public MyPage()
056: * {
057: * add(new Label("myMessage", "Hello!"));
058: * add(new Link("toYourPage")
059: * {
060: * public void onClick()
061: * {
062: * setResponsePage(new YourPage("Hi!"));
063: * }
064: * });
065: * }
066: * }
067: * </pre>
068: *
069: * <pre>
070: * // test code
071: * private WicketTester tester;
072: *
073: * public void setUp()
074: * {
075: * tester = new WicketTester();
076: * }
077: *
078: * public void testRenderMyPage()
079: * {
080: * //start and render the test page
081: * tester.startPage(MyPage.class);
082: * //assert rendered page class
083: * tester.assertRenderedPage(MyPage.class);
084: * //assert rendered label component
085: * tester.assertLabel("myMessage", "Hello!");
086: * }
087: * </pre>
088: *
089: * Above example is straight forward: start MyPage.class and assert Label it
090: * rendered. Next, we try to navigate through link:
091: *
092: * <pre>
093: * // production page
094: * public class YourPage extends WebPage
095: * {
096: * public YourPage(String message)
097: * {
098: * add(new Label("yourMessage", message));
099: * info("Wicket Rocks ;-)");
100: * }
101: * }
102: *
103: * //test code
104: * public void testLinkToYourPage()
105: * {
106: * tester.startPage(MyPage.class);
107: * //click link and render
108: * tester.clickLink("toYourPage");
109: * tester.assertRenderedPage(YourPage.class);
110: * tester.assertLabel("yourMessage", "Hi!");
111: * }
112: * </pre>
113: *
114: * <code>tester.clickLink(path);</code> will simulate user click on the
115: * component (in this case, it's a <code>Link</code>) and render the response
116: * page <code>YourPage</code>. Ok, unit test of <code>MyPage</code> is
117: * completed. Now we test <code>YourPage</code> standalone:
118: *
119: * <pre>
120: * //test code
121: * public void testRenderYourPage()
122: * {
123: * // provide page instance source for WicketTester
124: * tester.startPage(new TestPageSource()
125: * {
126: * public Page getTestPage()
127: * {
128: * return new YourPage("mock message");
129: * }
130: * });
131: * tester.assertRenderedPage(YourPage.class);
132: * tester.assertLabel("yourMessage", "mock message");
133: * // assert feedback messages in INFO Level
134: * tester.assertInfoMessages(new String[] { "Wicket Rocks ;-)" });
135: * }
136: * </pre>
137: *
138: * Instead of <code>tester.startPage(pageClass)</code>, we define a
139: * {@link org.apache.wicket.util.tester.ITestPageSource} to provide testing page
140: * instance for WicketTester. This is necessary because <code>YourPage</code>
141: * uses a custom constructor, which is very common for transferring model data,
142: * but can not be instantiated by reflection. Finally, we use
143: * <code>assertInfoMessages</code> to assert there is a feedback message
144: * "Wicket Rocks ;-)" in INFO level.
145: *
146: * TODO General: Example usage of FormTester
147: *
148: * @author Ingram Chen
149: * @author Juergen Donnerstag
150: * @author Frank Bille
151: */
152: public class WicketTester extends BaseWicketTester {
153: /**
154: * Default dummy web application for testing. Uses {@link HttpSessionStore}
155: * to store pages and the session.
156: */
157: public static class DummyWebApplication extends WebApplication {
158: /**
159: * @see org.apache.wicket.Application#getHomePage()
160: */
161: public Class getHomePage() {
162: return DummyHomePage.class;
163: }
164:
165: protected ISessionStore newSessionStore() {
166: // Don't use a filestore, or we spawn lots of threads, which makes
167: // things slow.
168: return new HttpSessionStore(this );
169: }
170:
171: /**
172: * @see org.apache.wicket.protocol.http.WebApplication#newWebResponse(javax.servlet.http.HttpServletResponse)
173: */
174: protected WebResponse newWebResponse(
175: final HttpServletResponse servletResponse) {
176: return new WebResponse(servletResponse);
177: }
178:
179: protected void outputDevelopmentModeWarning() {
180: // do nothing
181: }
182: }
183:
184: /**
185: * Dummy web application that does not support back button support but is
186: * cheaper to use for unit tests. Uses {@link SecondLevelCacheSessionStore}
187: * with a noop {@link IPageStore}.
188: */
189: public static class NonPageCachingDummyWebApplication extends
190: DummyWebApplication {
191: protected ISessionStore newSessionStore() {
192: return new SecondLevelCacheSessionStore(this ,
193: new IPageStore() {
194: public void destroy() {
195: }
196:
197: public Page getPage(String sessionId,
198: String pagemap, int id,
199: int versionNumber, int ajaxVersionNumber) {
200: return null;
201: }
202:
203: public void pageAccessed(String sessionId,
204: Page page) {
205: }
206:
207: public void removePage(String sessionId,
208: String pagemap, int id) {
209: }
210:
211: public void storePage(String sessionId,
212: Page page) {
213: }
214:
215: public void unbind(String sessionId) {
216: }
217:
218: public boolean containsPage(String sessionId,
219: String pageMapName, int pageId,
220: int pageVersion) {
221: return false;
222: }
223: });
224: }
225: }
226:
227: /** log. */
228: private static final Logger log = LoggerFactory
229: .getLogger(WicketTester.class);
230:
231: /**
232: * Create WicketTester and automatically create a WebApplication, but the
233: * tester will have no home page.
234: */
235: public WicketTester() {
236: this (new DummyWebApplication());
237: }
238:
239: /**
240: * Create WicketTester and automatically create a WebApplication.
241: *
242: * @param homePage
243: */
244: public WicketTester(final Class homePage) {
245: this (new WebApplication() {
246: /**
247: * @see org.apache.wicket.Application#getHomePage()
248: */
249: public Class getHomePage() {
250: return homePage;
251: }
252:
253: protected ISessionStore newSessionStore() {
254: // Don't use a filestore, or we spawn lots of threads, which
255: // makes things slow.
256: return new HttpSessionStore(this );
257: }
258:
259: protected WebResponse newWebResponse(
260: final HttpServletResponse servletResponse) {
261: return new WebResponse(servletResponse);
262: }
263:
264: protected void outputDevelopmentModeWarning() {
265: // Do nothing.
266: }
267: });
268: }
269:
270: /**
271: * Create WicketTester
272: *
273: * @param application
274: * The wicket tester object
275: */
276: public WicketTester(final WebApplication application) {
277: this (application, null);
278: }
279:
280: /**
281: * Create WicketTester to help unit testing
282: *
283: * @param application
284: * The wicket tester object
285: * @param path
286: * The absolute path on disk to the web application contents
287: * (e.g. war root) - may be null
288: *
289: * @see org.apache.wicket.protocol.http.MockWebApplication#MockWebApplication(String)
290: */
291: public WicketTester(final WebApplication application,
292: final String path) {
293: super (application, path);
294:
295: // We need to turn this on for unit testing so that url encoding will be
296: // done on sorted maps of parameters and they will string compare
297: UnitTestSettings.setSortUrlParameters(true);
298: }
299:
300: /**
301: * Assert that the ajax location header is present
302: */
303: public void assertAjaxLocation() {
304: if (null != ((MockHttpServletResponse) getWicketResponse()
305: .getHttpServletResponse()).getRedirectLocation()) {
306: throw new AssertionFailedError(
307: "Location header should *not* be present when using Ajax");
308: }
309:
310: String ajaxLocation = ((MockHttpServletResponse) getWicketResponse()
311: .getHttpServletResponse()).getHeader("Ajax-Location");
312: if (null == ajaxLocation) {
313: throw new AssertionFailedError(
314: "Ajax-Location header should be present when using Ajax");
315: }
316:
317: int statusCode = ((MockHttpServletResponse) getWicketResponse()
318: .getHttpServletResponse()).getStatus();
319: if (statusCode != 200) {
320: throw new AssertionFailedError(
321: "Expected HTTP status code to be 200 (OK)");
322: }
323: }
324:
325: /**
326: * assert component class
327: *
328: * @param path
329: * path to component
330: * @param expectedComponentClass
331: * expected component class
332: */
333: public void assertComponent(String path,
334: Class expectedComponentClass) {
335: assertResult(isComponent(path, expectedComponentClass));
336: }
337:
338: /**
339: * Test that a component has been added to a AjaxRequestTarget, using
340: * {@link AjaxRequestTarget#addComponent(Component)}. This method actually
341: * tests that a component is on the AJAX response sent back to the client.
342: * <p>
343: * PLEASE NOTE! This method doesn't actually insert the component in the
344: * client DOM tree, using javascript. But it shouldn't be needed because you
345: * have to trust that the Wicket Ajax Javascript just works.
346: *
347: * @param component
348: * The component to test whether it's on the response.
349: */
350: public void assertComponentOnAjaxResponse(Component component) {
351: Result result = isComponentOnAjaxResponse(component);
352: assertResult(result);
353: }
354:
355: /**
356: * Test that a component has been added to a AjaxRequestTarget, using
357: * {@link AjaxRequestTarget#addComponent(Component)}. This method actually
358: * tests that a component is on the AJAX response sent back to the client.
359: * <p>
360: * PLEASE NOTE! This method doesn't actually insert the component in the
361: * client DOM tree, using javascript. But it shouldn't be needed because you
362: * have to trust that the Wicket Ajax Javascript just works.
363: *
364: * @param componentPath
365: * The component path to the component to test whether it's on
366: * the response.
367: */
368: public void assertComponentOnAjaxResponse(String componentPath) {
369: assertComponentOnAjaxResponse(getComponentFromLastRenderedPage(componentPath));
370: }
371:
372: /**
373: * assert the content of last rendered page contains(matches) regex pattern.
374: *
375: * @param pattern
376: * reqex pattern to match
377: */
378: public void assertContains(String pattern) {
379: assertResult(ifContains(pattern));
380: }
381:
382: /**
383: * assert error feedback messages
384: *
385: * @param expectedErrorMessages
386: * expected error messages
387: */
388: public void assertErrorMessages(String[] expectedErrorMessages) {
389: List actualMessages = getMessages(FeedbackMessage.ERROR);
390: List msgs = new ArrayList();
391: for (Iterator iterator = actualMessages.iterator(); iterator
392: .hasNext();) {
393: msgs.add(iterator.next().toString());
394: }
395: WicketTesterHelper.assertEquals(Arrays
396: .asList(expectedErrorMessages), msgs);
397: }
398:
399: /**
400: * assert info feedback message
401: *
402: * @param expectedInfoMessages
403: * expected info messages
404: */
405: public void assertInfoMessages(String[] expectedInfoMessages) {
406: List actualMessages = getMessages(FeedbackMessage.INFO);
407: WicketTesterHelper.assertEquals(Arrays
408: .asList(expectedInfoMessages), actualMessages);
409: }
410:
411: /**
412: * assert component invisible.
413: *
414: * @param path
415: * path to component
416: */
417: public void assertInvisible(String path) {
418: assertResult(isInvisible(path));
419: }
420:
421: /**
422: * assert the text of <code>Label</code> component.
423: *
424: * @param path
425: * path to <code>Label</code> component
426: * @param expectedLabelText
427: * expected label text
428: */
429: public void assertLabel(String path, String expectedLabelText) {
430: Label label = (Label) getComponentFromLastRenderedPage(path);
431: Assert.assertEquals(expectedLabelText, label
432: .getModelObjectAsString());
433: }
434:
435: /**
436: * assert the model of {@link ListView} use expectedList
437: *
438: * @param path
439: * path to {@link ListView} component
440: * @param expectedList
441: * expected list in the model of {@link ListView}
442: */
443: public void assertListView(String path, List expectedList) {
444: ListView listView = (ListView) getComponentFromLastRenderedPage(path);
445: WicketTesterHelper.assertEquals(expectedList, listView
446: .getList());
447: }
448:
449: /**
450: * assert no error feedback messages
451: */
452: public void assertNoErrorMessage() {
453: List messages = getMessages(FeedbackMessage.ERROR);
454: Assert.assertTrue("expect no error message, but contains\n"
455: + WicketTesterHelper.asLined(messages), messages
456: .isEmpty());
457: }
458:
459: /**
460: * assert no info feedback messages
461: */
462: public void assertNoInfoMessage() {
463: List messages = getMessages(FeedbackMessage.INFO);
464: Assert.assertTrue("expect no info message, but contains\n"
465: + WicketTesterHelper.asLined(messages), messages
466: .isEmpty());
467: }
468:
469: /**
470: * assert <code>PageLink</code> link to page class.
471: *
472: * @param path
473: * path to <code>PageLink</code> component
474: * @param expectedPageClass
475: * expected page class to link
476: */
477: public void assertPageLink(String path, Class expectedPageClass) {
478: assertResult(isPageLink(path, expectedPageClass));
479: }
480:
481: /**
482: * assert last rendered Page class
483: *
484: * @param expectedRenderedPageClass
485: * expected class of last renered page
486: */
487: public void assertRenderedPage(Class expectedRenderedPageClass) {
488: assertResult(isRenderedPage(expectedRenderedPageClass));
489: }
490:
491: /**
492: * Assert last rendered Page against an expected HTML document
493: * <p>
494: * Use <code>-Dwicket.replace.expected.results=true</code> to
495: * automatically replace the expected output file.
496: * </p>
497: *
498: * @param clazz
499: * Used to load the file (relative to clazz package)
500: * @param filename
501: * Expected output
502: * @throws Exception
503: */
504: public void assertResultPage(final Class clazz,
505: final String filename) throws Exception {
506: String document = getServletResponse().getDocument();
507: setupRequestAndResponse();
508: DiffUtil.validatePage(document, clazz, filename, true);
509: }
510:
511: /**
512: * assert last rendered Page against an expected HTML document as a String
513: *
514: * @param expectedDocument
515: * Expected output
516: * @throws Exception
517: */
518: public void assertResultPage(final String expectedDocument)
519: throws Exception {
520: // Validate the document
521: String document = getServletResponse().getDocument();
522: Assert.assertTrue(document.equals(expectedDocument));
523: }
524:
525: /**
526: * assert component visible.
527: *
528: * @param path
529: * path to component
530: */
531: public void assertVisible(String path) {
532: assertResult(isVisible(path));
533: }
534:
535: private void assertResult(Result result) {
536: if (result.wasFailed()) {
537: throw new AssertionFailedError(result.getMessage());
538: }
539: }
540: }
|