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.cocoon;
018:
019: import com.gargoylesoftware.htmlunit.Page;
020: import com.gargoylesoftware.htmlunit.SubmitMethod;
021: import com.gargoylesoftware.htmlunit.WebClient;
022: import com.gargoylesoftware.htmlunit.WebRequestSettings;
023: import com.gargoylesoftware.htmlunit.WebResponse;
024: import com.gargoylesoftware.htmlunit.html.HtmlPage;
025: import com.gargoylesoftware.htmlunit.html.xpath.HtmlUnitXPath;
026: import com.gargoylesoftware.htmlunit.xml.XmlPage;
027:
028: import junit.framework.TestCase;
029:
030: import org.apache.avalon.framework.logger.ConsoleLogger;
031: import org.apache.avalon.framework.logger.Logger;
032:
033: import org.apache.commons.httpclient.methods.DeleteMethod;
034: import org.apache.commons.httpclient.methods.PutMethod;
035: import org.apache.commons.httpclient.methods.StringRequestEntity;
036:
037: import org.apache.commons.io.FileUtils;
038:
039: import org.jaxen.SimpleNamespaceContext;
040: import org.jaxen.XPath;
041: import org.jaxen.dom.DOMXPath;
042:
043: import org.w3c.dom.Document;
044:
045: import java.io.File;
046: import java.net.URL;
047: import java.util.HashMap;
048: import java.util.Map;
049:
050: /**
051: * Base class to run test cases on Cocoon samples.
052: * <p>
053: * This class extends the JUnit TestCase class to setup an environment which
054: * makes it possible to easily test Cocoon pages.
055: * First call one of the load methods and then assert on the response object,
056: * XML document (@see loadXmlPage), or HTML document (@see loadHtmlPage).
057: * </p>
058: * <p>
059: * Examples:
060: * </p>
061: * <pre>
062: * public void testStatus() {
063: * loadResponse("/samples/test/status");
064: * assertEquals("Status code", 200, response.getStatusCode());
065: * }
066: *
067: * public void testTitle() {
068: * loadHtmlPage("/samples/test/title");
069: * assertXPath("html/head/title"", "The Title");
070: * }
071: * </pre>
072: *
073: * <p>
074: * For loading XML and HTML documents currently on GET requests with
075: * optional querystring are supported. Please add POST requests and
076: * request parameters when you need them.
077: * </p>
078: * @version $Id: $
079: */
080: public abstract class HtmlUnitTestCase extends TestCase {
081: /**
082: * Logger for informative output by test cases.
083: * The default log level is WARN but may be changed by setting the
084: * property junit.test.loglevel to a different numeric value.
085: */
086: protected Logger logger;
087:
088: /**
089: * Base URL of the running Cocoon server which is to be tested.
090: * Set by property htmlunit.test.baseurl usually as http://localhost:8888/.
091: */
092: protected URL baseURL;
093:
094: /**
095: * Low-level access to WebClient object.
096: */
097: protected WebClient webClient;
098:
099: /**
100: * Low-level access to WebResponse object.
101: */
102: protected WebResponse response;
103:
104: /**
105: * Low-level access to XML document (org.w3c.dom.Document) or HTML document
106: * (com.gargoylesoftware.htmlunit.html.HtmlPage).
107: */
108: protected Object document;
109:
110: /**
111: * Low-level access to namespace mappings for XPath expressions.
112: */
113: protected Map namespaces = new HashMap();
114:
115: /* (non-Javadoc)
116: * @see junit.framework.TestCase#setUp()
117: */
118: protected void setUp() throws Exception {
119: super .setUp();
120:
121: String level = System.getProperty("junit.test.loglevel", ""
122: + ConsoleLogger.LEVEL_WARN);
123: this .logger = new ConsoleLogger(Integer.parseInt(level));
124:
125: String baseurl = System.getProperty("htmlunit.test.baseurl");
126: this .baseURL = new URL(baseurl);
127: this .webClient = new WebClient();
128: this .webClient.setRedirectEnabled(false);
129: this .namespaces.clear();
130: }
131:
132: /* (non-Javadoc)
133: * @see junit.framework.TestCase#tearDown()
134: */
135: protected void tearDown() throws Exception {
136: this .response = null;
137: this .document = null;
138:
139: super .tearDown();
140: }
141:
142: /**
143: * Sends HTTP GET request and loads response object.
144: */
145: protected void loadResponse(String pageURL) throws Exception {
146: WebRequestSettings webRequestSettings = new WebRequestSettings(
147: new URL(baseURL, pageURL), SubmitMethod.GET);
148: this .response = webClient.loadWebResponse(webRequestSettings);
149: }
150:
151: /**
152: * Sends HTTP DELETE request and loads response object.
153: */
154: protected void loadDeleteResponse(String pageURL) throws Exception {
155: URL url = new URL(baseURL, pageURL);
156: DeleteMethod method = new DeleteMethod(url.toExternalForm());
157: this .response = new HttpClientResponse(url, method);
158: }
159:
160: /**
161: * Sends HTTP PUT request and loads response object.
162: */
163: protected void loadPutResponse(String pageURL, String content)
164: throws Exception {
165: URL url = new URL(baseURL, pageURL);
166: PutMethod method = new PutMethod(url.toExternalForm());
167: method.setRequestEntity(new StringRequestEntity(content));
168: this .response = new HttpClientResponse(url, method);
169: }
170:
171: /**
172: * Sends HTTP request and parses response as HTML document.
173: */
174: protected void loadHtmlPage(String pageURL) throws Exception {
175: URL url = new URL(baseURL, pageURL);
176: Page page = webClient.getPage(url);
177: this .response = page.getWebResponse();
178: assertTrue("Response should be an HTML page",
179: page instanceof HtmlPage);
180: this .document = page;
181: assertNotNull("Response contains invalid HTML", this .document);
182: }
183:
184: /**
185: * Sends HTTP request and parses response as XML document.
186: */
187: protected void loadXmlPage(String pageURL) throws Exception {
188: URL url = new URL(baseURL, pageURL);
189: Page page = webClient.getPage(url);
190: this .response = page.getWebResponse();
191: assertTrue("Response should be an XML page",
192: page instanceof XmlPage);
193: XmlPage xmlPage = (XmlPage) page;
194: this .document = xmlPage.getXmlDocument();
195: assertNotNull("Response contains invalid XML", this .document);
196: }
197:
198: /**
199: * Returns XPath expression as string.
200: *
201: * @param xpathExpr XPath expression
202: *
203: * @return Value of XPath expression in current document.
204: * Empty string if XPath not matched.
205: */
206: protected String evalXPath(String xpathExpr) throws Exception {
207: XPath xpath = null;
208: if (document == null)
209: return null;
210: else if (document instanceof HtmlPage)
211: xpath = new HtmlUnitXPath(xpathExpr);
212: else if (document instanceof Document)
213: xpath = new DOMXPath(xpathExpr);
214: else
215: fail("Document type " + document.getClass().getName());
216:
217: xpath
218: .setNamespaceContext(new SimpleNamespaceContext(
219: namespaces));
220:
221: return xpath.stringValueOf(document);
222: }
223:
224: /**
225: * Add a namespace mapping for XPath expressions.
226: */
227: protected void addNamespace(String prefix, String uri)
228: throws Exception {
229: namespaces.put(prefix, uri);
230: }
231:
232: /**
233: * Assert that XPath expression result matches exactly expected value.
234: */
235: protected void assertXPath(String xpathExpr, String expected)
236: throws Exception {
237: assertEquals(xpathExpr, expected, evalXPath(xpathExpr));
238: }
239:
240: /**
241: * Copy file from webapp source to deployment area filtering content
242: * to replace parameter by value.
243: * The source and deployment directories are defined by the properties
244: * htmlunit.test.source-dir and htmlunit.test.deploy-dir.
245: *
246: * This method is most useful for testing the automatic reloading of
247: * changed files.
248: */
249: protected void copyWebappFile(String filename, String param,
250: String value) throws Exception {
251: String srcdir = System.getProperty("htmlunit.test.source-dir");
252: String dstdir = System.getProperty("htmlunit.test.deploy-dir");
253: File srcfile = new File(srcdir + "/" + filename);
254: File dstfile = new File(dstdir + "/" + filename);
255:
256: final String encoding = "ISO-8859-1";
257: StringBuffer content = new StringBuffer(FileUtils
258: .readFileToString(srcfile, encoding));
259:
260: int index = content.indexOf(param);
261: while (index != -1) {
262: content.replace(index, index + param.length(), value);
263: index = content.indexOf(param, index + 1);
264: }
265:
266: FileUtils.writeStringToFile(dstfile, content.toString(),
267: encoding);
268:
269: // Leave server some time to realize that file has changed.
270: Thread.sleep(1000);
271: }
272: }
|