1 // Copyright 2004-2007 Castle Project - http://www.castleproject.org/
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle
.MonoRail
.TestSupport
21 using System
.Text
.RegularExpressions
;
22 using System
.Web
.Hosting
;
23 using System
.Configuration
;
24 using System
.Collections
.Specialized
;
25 using System
.Runtime
.Serialization
.Formatters
.Binary
;
28 using NUnit
.Framework
;
31 /// Base class for tests cases using the ASP.Net Runtime
32 /// to run the web project. Deprecated.
34 [Obsolete("Prefer the BaseControllerTest")]
35 public abstract class AbstractMRTestCase
37 private static readonly String PhysicalWebDirConfigKey
= "web.physical.dir";
38 private static readonly String VirtualWebDirConfigKey
= "web.virtual.dir";
40 private WebAppHost host
;
41 private TestRequest request
;
42 private TestResponse response
;
43 private StringBuilder outputBuffer
= new StringBuilder();
45 #region Public Methods
48 /// Reinstates the request.
50 /// <param name="serializedRequest">The serialized request.</param>
51 public void ReinstateRequest(byte[] serializedRequest
)
54 MemoryStream ms
= null;
57 ms
= new MemoryStream(serializedRequest
);
59 bf
= new BinaryFormatter();
60 Request
.Headers
= (NameValueCollection
) bf
.Deserialize(ms
);
65 if (ms
!= null) ms
.Close();
70 /// Gets the serialized request.
72 /// <returns></returns>
73 public byte[] GetSerializedRequest()
75 MemoryStream objMS
= new MemoryStream();
76 BinaryFormatter objBinaryFormatter
= new BinaryFormatter();
80 objBinaryFormatter
.Serialize(objMS
, Request
.Headers
);
86 return objMS
.GetBuffer();
91 #region Test Lifecycle
94 public virtual void FixtureInitialize()
96 String virDir
= GetVirtualDir();
97 String physicalDir
= GetPhysicalDir();
99 if (!Path
.IsPathRooted(physicalDir
))
101 DirectoryInfo dinfo
= new DirectoryInfo(
102 Path
.Combine(AppDomain
.CurrentDomain
.SetupInformation
.ApplicationBase
, physicalDir
));
104 physicalDir
= dinfo
.FullName
;
107 if (!Directory
.Exists(Path
.Combine(physicalDir
, "bin")) ||
108 !File
.Exists(Path
.Combine(physicalDir
, "web.config")))
110 String message
= String
.Format("The path specified for the " +
111 "web project doesnt look as a web project dir (bin directory or web.config missing): {0}",
113 throw new ConfigurationErrorsException(message
);
116 host
= (WebAppHost
) ApplicationHost
.CreateApplicationHost(
117 typeof(WebAppHost
), virDir
, physicalDir
);
119 host
.Configure(virDir
, physicalDir
);
123 public virtual void Initialize()
125 request
= new TestRequest();
129 public virtual void Terminate()
133 [TestFixtureTearDown
]
134 public virtual void FixtureTerminate()
136 if (host
!= null) host
.Dispose();
144 /// Performs a GET operation on the specified path.
148 /// DoGet("home/index.rails");
151 /// <param name="path">The resource being request, for example <c>home/index.rails</c></param>
152 /// <param name="queryStringParams">A list of key/value pair, for example <c>name=johndoe</c></param>
153 public void DoGet(String path
, params String
[] queryStringParams
)
155 DoGet(path
, true, queryStringParams
);
159 /// Performs a GET operation on the specified path.
162 /// DoGet("home/index.rails");</code>
164 /// <param name="path">The resource being request, for example <c>home/index.rails</c></param>
165 /// <param name="resendCookies">if set to <c>true</c> [resend cookies].</param>
166 /// <param name="queryStringParams">A list of key/value pair, for example <c>name=johndoe</c></param>
167 public void DoGet(String path
, bool resendCookies
, params String
[] queryStringParams
)
169 AssertPathIsValid(path
);
171 if (queryStringParams
.Length
!= 0)
173 Request
.QueryStringParams
= queryStringParams
;
177 Request
.QueryStringParams
= null;
186 Request
.Verb
= "GET";
187 Request
.PostParams
= null;
193 /// Performs a POST operation on the specified path.
197 /// DoPost("producto/search.rails", "name=mac", "page=1");
200 /// <param name="path">The resource being request, for example <c>home/index.rails</c></param>
201 /// <param name="postStringParams">A list of key/value pair, for example <c>name=johndoe</c></param>
202 public void DoPost(String path
, params String
[] postStringParams
)
204 DoPost(path
, true, postStringParams
);
208 /// Performs a POST operation on the specified path.
212 /// DoPost("producto/search.rails", "name=mac", "page=1");
215 /// <param name="path">The resource being request, for example <c>home/index.rails</c></param>
216 /// <param name="resendCookies">if set to <c>true</c> [resend cookies].</param>
217 /// <param name="postStringParams">A list of key/value pair, for example <c>name=johndoe</c></param>
218 public void DoPost(String path
, bool resendCookies
, params String
[] postStringParams
)
220 if (postStringParams
.Length
!= 0)
222 Request
.PostParams
= postStringParams
;
226 Request
.PostParams
= null;
229 outputBuffer
.Length
= 0;
231 int pos
= path
.IndexOf('?');
235 string qs
= path
.Substring(pos
+ 1);
236 path
= path
.Substring(0, pos
);
237 Request
.QueryStringParams
= qs
.Split('&');
241 Request
.QueryStringParams
= null;
244 // Set the content type so that the post data comes through.
245 Request
.Headers
.Add("Content-Type", "application/x-www-form-urlencoded");
248 Request
.Verb
= "POST";
259 /// Performs a HEAD operation on the specified path.
263 /// DoHead("producto/search.rails", "name=mac", "page=1");
266 /// <param name="path">The resource being request, for example <c>home/index.rails</c></param>
267 /// <param name="postStringParams">A list of key/value pair, for example <c>name=johndoe</c></param>
268 public void DoHead(String path
, params String
[] postStringParams
)
270 if (postStringParams
.Length
!= 0)
272 Request
.PostParams
= postStringParams
;
276 Request
.PostParams
= null;
279 outputBuffer
.Length
= 0;
281 int pos
= path
.IndexOf('?');
284 string qs
= path
.Substring(pos
+ 1);
285 path
= path
.Substring(0, pos
);
286 Request
.QueryStringParams
= qs
.Split('&');
290 Request
.QueryStringParams
= null;
294 Request
.Verb
= "HEAD";
304 /// Gets the <c>TestRequest</c>
306 public TestRequest Request
308 get { return request; }
312 /// Gets the <c>TestResponse</c>
314 public TestResponse Response
316 get { return response; }
320 /// Gets the request response
324 get { return outputBuffer.ToString(); }
328 /// Returns the sessionId related to the current session
330 public string SessionId
334 string sessionId
= string.Empty
;
339 Request
.Cookies
.GetCookies(new Uri("http://localhost"))["ASP.NET_SessionId"].ToString().Split("=".ToCharArray())[1
352 #region Available Asserts
355 /// Asserts that the response contains a number of nodes matching an XPath expression.
357 /// <param name="xpathExpression">The xpath expression to match against.</param>
358 /// <param name="numberOfExpectedNodes">The number of expected nodes.</param>
359 protected void AssertResponseNodeCount(String xpathExpression
, int numberOfExpectedNodes
)
361 XmlDocument xml
= new XmlDocument();
364 Assert
.AreEqual(numberOfExpectedNodes
, xml
.SelectNodes(xpathExpression
).Count
);
368 /// Asserts that the response was NOT a redirect to the specified
369 /// <c>url</c> - for example check that your request was not sent to a login screen.
371 protected void AssertNotRedirectedTo(String url
)
373 // Location header always starts with a leading / so
374 // if this is not present on the specified url, add it
376 if (url
.IndexOf("/") != 0)
381 Assert
.IsFalse(url
== (String
) Response
.Headers
["Location"]);
384 protected void AssertStatusCode(int expectedCode
)
386 Assert
.IsNotNull(response
, "No requests performed with DoGet or DoPost (?)");
387 Assert
.IsTrue(response
.StatusCode
== expectedCode
, "Expecting status code {0} when it was in fact {1} {2}",
388 expectedCode
, response
.StatusCode
, response
.StatusDescription
);
392 /// Asserts the return status code is less than 400
394 protected void AssertSuccess()
396 Assert
.IsNotNull(response
, "No requests performed with DoGet or DoPost (?)");
397 Assert
.IsTrue(response
.StatusCode
< 400, "Expecting status code < 400 when it was in fact {0} - {1}",
398 response
.StatusCode
, response
.StatusDescription
);
402 /// Asserts that reply has exactly the samme
403 /// content of <c>expectedContents</c>
405 /// <param name="expectedContents"></param>
406 protected void AssertReplyEqualTo(String expectedContents
)
408 Assert
.AreEqual(expectedContents
, Output
, "Reply differs. Expecting {0} but was {1}", expectedContents
, Output
);
412 /// Asserts that reply starts with
413 /// <c>expectedContents</c>
415 protected void AssertReplyStartsWith(String contents
)
417 String buffer
= Output
;
419 Assert
.IsTrue(buffer
.StartsWith(contents
),
420 String
.Format("Reply string did not start with '{0}'. It was '{1}'", Prepare(contents
),
421 Prepare(buffer
.Substring(0, Math
.Min(contents
.Length
, buffer
.Length
)))));
425 /// Asserts that reply ends with
426 /// <c>expectedContents</c>
428 protected void AssertReplyEndsWith(String contents
)
430 String buffer
= Output
;
432 Assert
.IsTrue(buffer
.EndsWith(contents
),
433 "Reply string did not end with '{0}'. It was '{1}'", contents
,
434 buffer
.Substring(0, Math
.Min(contents
.Length
, buffer
.Length
)));
438 /// Asserts that reply contains the specified
439 /// <c>expectedContents</c>
441 protected void AssertReplyContains(String contents
)
443 Assert
.IsTrue(Output
.IndexOf(contents
) != -1,
444 "AssertReplyContains did not find the content '{0}'. Raw content '{1}'", contents
, Output
);
448 /// Asserts that reply have only whitespace characters
450 protected void AssertReplyIsBlank()
452 string contents
= Output
.Trim();
454 Assert
.IsTrue(contents
== String
.Empty
,
455 "AssertReplyIsBlank found not whitespace characters '{0}'", contents
);
459 /// Asserts that reply contents match the specified pattern, ignoring any whitespaces
462 protected void AssertReplyMatch(String pattern
)
464 AssertReplyMatch(pattern
, true, RegexOptions
.None
);
468 /// Asserts that reply contents match the specified pattern
471 protected void AssertReplyMatch(String pattern
, bool ignoreSpaces
)
473 AssertReplyMatch(pattern
, ignoreSpaces
, RegexOptions
.None
);
477 /// Asserts that reply contents match the specified pattern
480 protected void AssertReplyMatch(String pattern
, bool ignoreSpaces
, RegexOptions options
)
482 string contents
= Output
;
486 contents
= Regex
.Replace(contents
, @"\s+", "");
487 pattern
= Regex
.Replace(pattern
, @"\s+", "");
490 Regex re
= new Regex(pattern
, options
);
492 Assert
.IsTrue(re
.IsMatch(contents
),
493 "AssertReplyMatch did not match pattern '{0}'. Raw content {1}", pattern
, contents
);
497 /// Asserts that reply does not contain
498 /// <c>expectedContents</c>
500 protected void AssertReplyDoesNotContain(String contents
)
502 Assert
.IsTrue(Output
.IndexOf(contents
) == -1,
503 "AssertReplyDoNotContain found the content '{0}'", contents
);
507 /// Asserts that the response was a redirect to the specified
510 protected void AssertRedirectedTo(String url
)
512 Assert
.AreEqual(302, Response
.StatusCode
, "Redirect status not used");
513 AssertHasHeader("Location");
514 Assert
.AreEqual(url
, Response
.Headers
["Location"]);
518 /// Asserts that the content-type header is equals to the specified
521 /// <param name="expectedContentType">value to assert to</param>
522 protected void AssertContentTypeEqualsTo(String expectedContentType
)
524 AssertHasHeader("Content-Type");
525 Assert
.AreEqual(expectedContentType
, Response
.Headers
["Content-Type"]);
529 /// Asserts that the content-type header starts with to the specified
532 /// <param name="expectedContentType">value to assert to</param>
533 protected void AssertContentTypeStartsWith(String expectedContentType
)
535 AssertHasHeader("Content-Type");
536 Assert
.IsTrue(Response
.Headers
["Content-Type"].ToString().StartsWith(expectedContentType
));
540 /// Asserts that the content-type header ends with the specified
543 /// <param name="expectedContentType">value to assert to</param>
544 protected void AssertContentTypeEndsWith(String expectedContentType
)
546 AssertHasHeader("Content-Type");
547 Assert
.IsTrue(Response
.Headers
["Content-Type"].ToString().EndsWith(expectedContentType
));
551 /// Asserts that response contains the specified header.
553 /// <param name="headerName">value to assert to</param>
554 protected void AssertHasHeader(String headerName
)
556 Assert
.IsTrue(Response
.Headers
[headerName
] != null,
557 "Header '{0}' was not found", headerName
);
561 /// Asserts that PropertyBag contains the specified key.
563 /// <param name="entryKey">key name</param>
564 protected void AssertPropertyBagContains(String entryKey
)
566 Assert
.IsNotNull(response
.PropertyBag
,
567 "PropertyBag could not be used. Are you using a testing enable version of MonoRail Engine and Framework?");
568 Assert
.IsTrue(response
.PropertyBag
.Contains(entryKey
), "Entry {0} was not on PropertyBag", entryKey
);
572 /// Asserts that PropertyBag's entry value equals to the specified value.
574 /// <param name="entryKey">key name</param>
575 /// <param name="expectedValue">value to assert to</param>
576 protected void AssertPropertyBagEntryEquals(String entryKey
, object expectedValue
)
578 AssertPropertyBagContains(entryKey
);
579 Assert
.AreEqual(expectedValue
, response
.PropertyBag
[entryKey
], "PropertyBag entry differs from the expected");
583 /// Asserts that Flash contains the specified key.
585 /// <param name="entryKey">key name</param>
586 protected void AssertFlashContains(String entryKey
)
588 Assert
.IsNotNull(response
.Flash
,
589 "Flash could not be used. Are you using a testing enable version of MonoRail Engine and Framework?");
590 Assert
.IsTrue(response
.Flash
.Contains(entryKey
), "Entry {0} was not on Flash", entryKey
);
594 /// Asserts that Flash does not contains the specified key.
596 /// <param name="entryKey">key name</param>
597 protected void AssertFlashDoesNotContain(String entryKey
)
599 Assert
.IsNotNull(response
.Flash
,
600 "Flash could not be used. Are you using a testing enable version of MonoRail Engine and Framework?");
601 Assert
.IsFalse(response
.Flash
.Contains(entryKey
), "Entry {0} was on Flash", entryKey
);
605 /// Asserts that Flash's entry value equals to the specified value.
607 /// <param name="entryKey">key name</param>
608 /// <param name="expectedValue">value to assert to</param>
609 protected void AssertFlashEntryEquals(String entryKey
, object expectedValue
)
611 AssertFlashContains(entryKey
);
612 Assert
.AreEqual(expectedValue
, response
.Flash
[entryKey
], "Flash entry differs from the expected");
616 /// Asserts that Session contains the specified key.
618 /// <param name="entryKey">key name</param>
619 protected void AssertSessionContains(String entryKey
)
621 Assert
.IsNotNull(response
.Session
,
622 "Session could not be used. Are you using a testing enable version of MonoRail Engine and Framework?");
623 Assert
.IsTrue(response
.Session
.Contains(entryKey
), "Entry {0} was not on Session", entryKey
);
627 /// Asserts that Session does not contains the specified key.
629 /// <param name="entryKey">key name</param>
630 protected void AssertSessionDoesNotContain(String entryKey
)
632 Assert
.IsNotNull(response
.Session
,
633 "Session could not be used. Are you using a testing enable version of MonoRail Engine and Framework?");
634 Assert
.IsFalse(response
.Session
.Contains(entryKey
), "Entry {0} was on Session", entryKey
);
638 /// Asserts that Session's entry value equals to the specified value.
640 /// <param name="entryKey">key name</param>
641 /// <param name="expectedValue">value to assert to</param>
642 protected void AssertSessionEntryEqualsTo(String entryKey
, object expectedValue
)
644 AssertSessionContains(entryKey
);
645 Assert
.AreEqual(expectedValue
, response
.Session
[entryKey
], "Session entry differs from the expected");
649 /// Asserts that the response contains the specified cookie.
651 /// <param name="cookieName">cookie name</param>
652 protected void AssertHasCookie(String cookieName
)
654 CookieCollection cookies
= Response
.Cookies
.GetCookies(new Uri("http://localhost"));
656 foreach(Cookie cookie
in cookies
)
658 if (cookie
.Name
.Equals(cookieName
)) return;
661 Assert
.Fail("Cookie '{0}' was not found", cookieName
);
665 /// Asserts that Response cookie entry value equals to the specified value.
667 /// <param name="cookieName">cookie name</param>
668 /// <param name="expectedValue">value to assert to</param>
669 protected void AssertCookieValueEqualsTo(String cookieName
, String expectedValue
)
671 AssertHasCookie(cookieName
);
673 CookieCollection cookies
= Response
.Cookies
.GetCookies(new Uri("http://localhost"));
675 foreach(Cookie cookie
in cookies
)
677 if (cookie
.Name
.Equals(cookieName
))
679 Assert
.AreEqual(expectedValue
, cookie
.Value
);
686 /// Asserts that the response cookie has the specified expiration.
688 /// <param name="cookieName">cookie name</param>
689 /// <param name="expectedExpiration">value to assert to</param>
690 protected void AssertCookieExpirationEqualsTo(String cookieName
, DateTime expectedExpiration
)
692 AssertHasCookie(cookieName
);
694 CookieCollection cookies
= Response
.Cookies
.GetCookies(new Uri("http://localhost"));
696 foreach(Cookie cookie
in cookies
)
698 if (cookie
.Name
.Equals(cookieName
))
700 Assert
.AreEqual(expectedExpiration
.Day
, cookie
.Expires
.Day
, "Expiration day differs. Expecting {0} but was {1}",
701 expectedExpiration
.Day
, cookie
.Expires
.Day
);
702 Assert
.AreEqual(expectedExpiration
.Month
, cookie
.Expires
.Month
,
703 "Expiration month differs. Expecting {0} but was {1}", expectedExpiration
.Month
,
704 cookie
.Expires
.Month
);
705 Assert
.AreEqual(expectedExpiration
.Year
, cookie
.Expires
.Year
, "Expiration year differs. Expecting {0} but was {1}",
706 expectedExpiration
.Year
, cookie
.Expires
.Year
);
707 Assert
.AreEqual(expectedExpiration
.Hour
, cookie
.Expires
.Hour
, "Expiration hour differs. Expecting {0} but was {1}",
708 expectedExpiration
.Hour
, cookie
.Expires
.Hour
);
709 Assert
.AreEqual(expectedExpiration
.Minute
, cookie
.Expires
.Minute
,
710 "Expiration minute differs. Expecting {0} but was {1}", expectedExpiration
.Minute
,
711 cookie
.Expires
.Minute
);
712 // Assert.AreEqual(expectedExpiration.Second, cookie.Expires.Second, "Expiration second differs. Expecting {0} but was {1}", expectedExpiration.Second, cookie.Expires.Second);
722 protected virtual string GetPhysicalDir()
724 String dir
= ConfigurationManager
.AppSettings
[PhysicalWebDirConfigKey
];
728 String message
= String
.Format("Could not find a configuration key " +
729 "defining the web application physical directory. You must create " +
730 "a key ('{0}') on your configuration file or override the method " +
731 "AbstractMRTestCase.GetPhysicalDir", PhysicalWebDirConfigKey
);
733 throw new ConfigurationErrorsException(message
);
739 protected virtual string GetVirtualDir()
741 String dir
= ConfigurationManager
.AppSettings
[VirtualWebDirConfigKey
];
753 private String
Prepare(String content
)
755 if (content
== null || content
.Length
== 0) return String
.Empty
;
757 return content
.Replace("\n", "\\n").Replace("\r", "\\r").Replace("\t", "\\t");
761 /// Ensures that cookies (and therefore the session) will persist between requests,
762 /// emulating the behaviour of a genuine web client.
764 private void ResendCookies()
766 // If no initial request has been made then there will be no response
767 // Therefore we do not need to resendcookies as none have been received
768 if (response
!= null)
770 Uri uri
= new Uri("http://localhost");
772 // We have a cookies container which is used to persist the cookies between requests,
773 // emulating the cookie cache on a web browser
775 // Here we take the cookies from the response and append them to any existing cookies
776 request
.Cookies
.Add(uri
, response
.Cookies
.GetCookies(uri
));
778 // Clear all of the cookie headers to prepare for them to be resent on the next request
779 request
.Headers
.Remove("Cookie");
781 // Form a new cookie header from the cookies in the persistant request cookie container
782 request
.Headers
.Add("Cookie", request
.Cookies
.GetCookieHeader(uri
));
786 private void SendRequest()
788 outputBuffer
= new StringBuilder();
790 StringWriter writer
= new StringWriter(outputBuffer
);
792 if (Request
.Headers
["IsTestWorkerRequest"] == null)
794 Request
.Headers
.Add("IsTestWorkerRequest", "true");
797 response
= host
.Process(Request
, writer
);
802 private void AssertPathIsValid(string path
)
805 throw new ArgumentNullException("path", "Can't test a null path");
806 if (path
.Length
== 0)
807 throw new ArgumentException("Can't test an empty path", "path");
809 throw new ArgumentException("Path cannot start with a '/'");