使用 WebDev.WebServer、NUnit 和 Salient.Web.HttpLib 测试 Http 端点






4.50/5 (2投票s)
保持理智!快速轻松地为您的 Ajax/REST/表单/上传/其他 http 端点编写测试套件。
保持理智!快速轻松地为您的 Ajax/REST/表单/上传/其他 http 端点编写测试套件。
在此处下载 HttpLib 和本教程的源代码:Salient.Web.HttpLib.b1.zip
概述
在本文中,我将向您介绍一个小型库,名为 Salient.Web.HttpLib
,它主要设计用于支持 Http 端点的测试。
特别关注测试 MS Ajax/WCF/WebServices 端点的问题,测试的主要重点将集中在 Ajax 启用的 WCF 服务、用 [ScriptService]
或 [ScriptMethod]
属性装饰的静态 .aspx PageMethods 和 Xml WebServices 上,它们都表现出相同的行为,并将在此后统称为“JSON 端点”。
其他端点/平台可以轻松处理,并且在遵循本教程之后,将上述模式应用于其他目标将是轻而易举的事。
本教程将通过详细描述随附 VS2008 解决方案中包含的实际通过的测试来完成。
本教程测试包含在 ArticleSiteFixture
中,该类继承自 Salient.Web.HttpLib.WebDevFixture
。
此测试夹具包含模拟客户端脚本预期请求类型的方法。这允许从客户端脚本的角度自动测试我们的端点。
注意:请注意:本着我们正在模拟的 JavaScript 客户端的精神,我将肆无忌惮地滥用闭包、匿名方法和类型。;-)
您可以称之为功能测试、冒烟测试,甚至集成测试,但不要称之为单元测试。
演练
Salient.Web.HttpLib.WebDevFixture
WebDevFixture
封装了 Visual Studio 开发服务器的可控启动。要启动 WebDev 实例,只需提供一个未使用的端口和站点的物理路径。这可以是一个相对路径,从而实现便携式测试。
WebDevFixture
提供了一个便捷方法 .NormalizeUri(),它可以将相对路径和查询字符串规范化或根植到当前站点的根目录。
有关更多信息,请参阅 https://codeproject.org.cn/Articles/72222/A-general-purpose-Visual-Studio-2008-Development-S.aspx。
有关更精细地控制测试服务器,您应该查看 http://cassinidev.codeplex.com/
此示例演示了 NUnit 与 WebDevFixture
的用法,但任何测试框架都可以轻松使用。
ArticleSiteFixture
[TestFixture]
public class ArticleSiteFixture : WebDevFixture
{
protected override int Port
{
get { return 12334; }
}
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
// the relative physical path of the site we want to test
var path = Path.GetFullPath(@"..\..\..\HttpLibArticleSite");
StartServer(path);
}
/*
*
* insert interesting tests go here
*
*/
}
反序列化 MS Ajax 包装的 JSON
JSON 端点响应的默认格式是“d: 包装”对象。有一些配置选项和属性可以修改此行为,但此默认行为也是最坏的情况,因此我们立即处理它。
我们将处理的一个示例是演示站点中定义的简单 Result
类。当返回为 JSON 时,它看起来像这样:
{"d":{"__type":"Result:#HttpLibArticleSite","Message":"Value pull OK.","Session":"rmyykw45zbkxxxzdun0juyfr","Value":"foo"}}
反序列化此格式的 JSON 可能会有问题,但我为 JavaScriptSerializer
提供了一个扩展方法,该方法可以处理此类型的 JSON。您可以在此处详细了解它:Parsing-ClientScript-JSON.aspx
简而言之,ClientScriptUtilities
提供了一个 JavaScriptSerializer
扩展方法 .CleanAndDeserialize<T>()
,该方法将提取内部对象并清理 "__type"
属性,然后对其进行反序列化。最终反序列化的 JSON 如下所示:
{"Message":"Value pull OK.","Session":"rmyykw45zbkxxxzdun0juyfr","Value":"foo"}
CleanAndDeserialize
也支持反序列化为匿名类型。
注意:CleanAndDeserialize
不需要丑陋的 MsAjax JSON。它可以处理任何有效的 JSON,因此无论 JSON 格式如何,它都可以安全地替换您代码中的 .Deserialize<T>
。
[Test]
public void Deserializing_MS_Ajax_Wrapped_JSON()
{
JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
// simulate a typical JSON response
const string responseText =
"{\"d\":{\"__type\":\"Result:#HttpLibArticleSite\",\"Message\":\"Value pull OK.\",\"Session\":\"rmyykw45zbkxxxzdun0juyfr\",\"Value\":\"foo\"}}";
Result result = jsSerializer.CleanAndDeserialize<Result>(responseText);
Assert.AreEqual("rmyykw45zbkxxxzdun0juyfr", result.Session);
var resultPrototype = new
{
Message = default(string),
Value = default(string)
};
var anonymousResultSubset = jsSerializer.CleanAndDeserialize(responseText, resultPrototype);
Assert.AreEqual("foo", anonymousResultSubset.Value);
}
异常处理
当任何类型的端点抛出异常时,它最终都会被一个通用的“500 Internal Server Error”WebException 吞没。
发现实际底层异常可能非常繁琐,涉及解析 WebException.Response.GetResponseStream()
并确定它是来自 JSON 端点的 JsonFaultDetail,还是 ASP.Net 页面崩溃(Yellow Screen of death),还是其他多种格式。
WebRequestException
封装了所有这些行为,并在可用时显示相关信息。原始 WebException
可在 InnerException
属性上找到。
[Test]
public void Exception_Handling()
{
try
{
RequestFactory.CreatePostJsonApp(NormalizeUri("AjaxService.svc/ThrowException")).GetResponse();
Assert.Fail("Expected WebException");
}
catch (WebException ex)
{
WebRequestException ex2 = WebRequestException.Create(ex);
Console.WriteLine("Caught {0}: {1}", ex2.ExceptionDetail.ExceptionType, ex2.Message);
}
}
RequestFactory.CreateRequest
RequestFactory.CreateRequest
是所有专用工厂方法重载最终调用的基础方法。
通过对该方法的基本理解,所有专用重载都将易于理解。
RequestFactory.CreateRequest
构建一个 HttpWebRequest
,适当地应用提供的参数并返回它。然后,您可以随意操作请求,然后再执行请求。此模式应用于所有各种重载。
参数
requestUri - Uri 或 String
资源的绝对 URI
method - HttpMethod
请求的 http 方法。当前支持的选项有HttpMethod.Post
和HttpMethod.Get
contentType - ContentType
请求的内容类型。当前支持的选项有ContentType.None
、ContentType.ApplicationForm
、ContentType.ApplicationJson
和ContentType.TextJson
postData - object
可选。一个NameValueCollection
或object
。匿名类型是可接受的。
对象将被适当地序列化以适应请求方法和内容类型,然后再应用于请求。
注意:任何形状与目标方法参数相似的对象都可以接受。方便的是,这包括匿名类型。
对于“Get”或表单“Post”,NameValueCollection
可能更合适,因为它能够接受单个键的多个值,从而完全模拟表单的可能形状。
在创建匿名类型作为输入参数时,您不必完全原型化目标类型。您必须原型化非可空属性,包括值类型和结构,但可以省略任何可空属性,包括您不需要发送的Nullable<T>
和引用类型。
适合方法public Result PutSessionVar(string input)
的 postData 的匿名类型将是var postData = new { input = "foo" };
适合方法public Result PutSessionVar(Result input)
的 postData 的匿名类型将是
var postData = new { input = new Result() };
或
var postData = new { input = new { Message = "message", Session = "session", Value = "value" } };
或
var postData = new { input = new { Message = "message" } };
- 对于表单“Post”请求,postData 对象将被序列化为 URL 编码的键值字符串,并流式传输到请求正文中。
- 对于 JSON“Post”,它将被 JSON 序列化并流式传输到请求正文中。
- 对于“Get”请求,它将被 URL 编码成一个查询字符串,并智能地附加到 Uri。如果 Uri 是裸露的,查询字符串将用“?”附加。如果 Uri 已经有查询字符串,则新查询将用“&”附加。
cookies - CookieCollection
可选。在请求之间共享CookieCollection
对于维护会话状态、FormsAuthentication 票证和其他 Cookie 是必需的。
headers - NameValueCollection
可选。要添加到请求的 Http 标头。
StreamExtensions
提供了两个简单的便捷扩展,可与响应流一起使用。
Stream.TextStream.Text()
将流提取为 UTF-8 字符串。Stream.Bytes()
将流提取为字节数组。
[Test]
public void Using_CreateRequest()
{
// Required arguments:
Uri uri = NormalizeUri("AjaxService.svc/PutSessionVar");
const HttpMethod method = HttpMethod.Post;
const ContentType contentType = ContentType.ApplicationJson;
// Optional arguments:
// This is the method we will be calling - public Result PutSessionVar(string input)
// Lets create an anonymous type shaped like the method signature...
var postData = new { input = "foo" };
CookieContainer cookies = new CookieContainer();
// Add a useful header...
NameValueCollection headers = new NameValueCollection { { "x-foo-header", "bar-value" } };
// With the arguments defined, create the request
HttpWebRequest request = RequestFactory.CreateRequest(uri, method, contentType, postData, cookies, headers);
// If you are not concerned with immediate disposal of the response stream,
// you can retrieve the response with a single expression like so
var responseText = request.GetResponse().GetResponseStream().Text();
// thats it for the basic usage.
// we are going to pick up the pace a bit and demonstrate a few usage patterns so lets declare an anonymous
// inline factory method to create our requests from this point on.
Func<HttpWebRequest> createRequest = () => RequestFactory.CreateRequest(uri, method, contentType, postData, cookies, headers);
using (Stream responseStream = createRequest().GetResponse().GetResponseStream())
{
var rtext = responseStream.Text();
}
// It is recommended that you wrap requests using the exception handling pattern previously described
// even if you are just going to re-throw
try
{
using (Stream responseStream = createRequest().GetResponse().GetResponseStream())
{
var rtext = responseStream.Text();
}
}
catch (WebException ex)
{
WebRequestException wrex = WebRequestException.Create(ex);
// Now either try to handle it or re-throw
throw wrex;
// we reset the stack trace but a WebRequestException has but one source so this is not an issue.
}
// Now if we throw the deserialization into the mix things start coming together.
JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
try
{
using (Stream responseStream = createRequest().GetResponse().GetResponseStream())
{
Result result1 = jsSerializer.CleanAndDeserialize<Result>(responseStream.Text());
Assert.AreEqual("foo", result1.Value);
}
}
catch (WebException ex)
{
WebRequestException wrex = WebRequestException.Create(ex);
throw wrex;
}
// Using an anonymous prototype for the response
try
{
using (Stream responseStream = createRequest().GetResponse().GetResponseStream())
{
var resultPrototype = new
{
Value = default(string),
Session = default(string)
};
var result2 = jsSerializer.CleanAndDeserialize(responseStream.Text(), resultPrototype);
Assert.AreEqual("foo", result2.Value);
}
}
catch (WebException ex)
{
WebRequestException wrex = WebRequestException.Create(ex);
throw wrex;
}
// For those who like to live dangerously the whole process can be executed in one statement
// Do not try this at home. ;-)
CookieContainer existingCookies = new CookieContainer();
var result3 = jsSerializer.CleanAndDeserialize(
RequestFactory.CreateRequest(NormalizeUri("AjaxService.svc/PutSessionVar"), HttpMethod.Post, ContentType.ApplicationJson,
new { input = "foo" }, existingCookies, new NameValueCollection { { "x-foo-header", "bar-value" } }).
GetResponse().GetResponseStream().Text(), new { Value = default(string) });
Assert.AreEqual("foo", result3.Value);
// interesting as this treatment may be, I think you would have to be mad to apply it.
}
现在,在对基础 .CreateRequest()
方法有了基本了解之后,让我们重构以减少代码冗余,并将我们的 JSON 响应执行模式集中到几个通用方法中。
/// <summary>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="request"></param>
/// <param name="handler">If null, created WebRequestException is thrown when GetReponse craps out.</param>
/// <returns></returns>
public T GetJsonResponse<T>(HttpWebRequest request, Action<WebRequestException> handler)
{
JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
try
{
using (Stream responseStream = request.GetResponse().GetResponseStream())
{
return jsSerializer.CleanAndDeserialize<T>(responseStream.Text());
}
}
catch (WebException ex)
{
WebRequestException reqEx = WebRequestException.Create(ex);
if (handler != null)
{
handler(reqEx);
return default(T);
}
throw reqEx;
}
}
/// <summary>
/// And for anonymous response types....
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="request"></param>
/// <param name="prototype"></param>
/// <param name="handler">If null, created WebRequestException is thrown when GetReponse craps out.</param>
/// <returns></returns>
public T GetJsonResponse<T>(HttpWebRequest request, T prototype, Action<WebRequestException> handler)
{
JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
try
{
using (Stream responseStream = request.GetResponse().GetResponseStream())
{
return jsSerializer.CleanAndDeserialize(responseStream.Text(), prototype);
}
}
catch (WebException ex)
{
WebRequestException reqEx = WebRequestException.Create(ex);
if (handler != null)
{
handler(reqEx);
return default(T);
}
throw reqEx;
}
}
/// <summary>
/// Test the utility methods.
/// </summary>
[Test]
public void Using_Utility_Methods()
{
// We have already seen how to build up a request from parts, so for the sake of
// brevity (and being clever) lets define an inline factory method.
Func<HttpWebRequest> createRequest = () => RequestFactory.CreateRequest(
NormalizeUri("AjaxService.svc/PutSessionVar"), HttpMethod.Post, ContentType.TextJson, new { input = "foo" }, null, null);
// As an added bit of abuse, an anonymous exception handler to pass to the generic utility methods
Action<WebRequestException> commonExceptionHandler = up =>
{
Console.WriteLine("Inside inline shared exception handler");
// handle ex if possible, and return, otherwise throw
throw up; // ;-O~~
};
// Call a valid method with referenced response type. commonExceptionHandler will not be executed
Result result = GetJsonResponse<Result>(createRequest(), commonExceptionHandler);
Assert.AreEqual("foo", result.Value);
// Call a valid method with an anonymous response type. commonExceptionHandler will not be executed
var anonResult = GetJsonResponse(createRequest(), new { Value = default(string) }, commonExceptionHandler);
Assert.AreEqual("foo", anonResult.Value);
// Test the generic GetJsonResponse with an exception and a handler, . commonExceptionHandler WILL executed
try
{
GetJsonResponse(RequestFactory.CreatePostJsonApp(NormalizeUri("AjaxService.svc/ThrowException")), new { Value = default(string) }, commonExceptionHandler);
Assert.Fail("Expected WebRequestException");
}
catch (WebRequestException)
{
Console.WriteLine("Handler works");
}
// test the generic GetJsonResponse with an exception and no handler. commonExceptionHandler is null and will NOT be executed
try
{
GetJsonResponse(RequestFactory.CreatePostJsonApp(NormalizeUri("AjaxService.svc/ThrowException")), new { Value = default(string) }, null);
Assert.Fail("Expected WebRequestException");
}
catch (WebRequestException)
{
Console.WriteLine("WebRequestException was thrown from GetJsonResponse as no handler was specified");
}
}
重载
现在,让我们花点时间简要回顾一下 CreateRequest
的可用重载,然后再深入研究 .CreatePostJsonApp()
。
所有重载都接受 string
或 Uri
作为 requestUri
。
有一个专用的 RequestFactory.CreateFilePost()
方法用于上传文件,它具有自己独特但类似 API,将在本教程的最后进行介绍。
/// <summary>
/// Creates a request using http 'Get' and ContentType.None. PostData is Url-encoded and appended to the Uri.
/// </summary>
[Test]
public void CreateGet_Overloads()
{
Uri requestUri = NormalizeUri("Default.aspx");
CookieContainer cookies = new CookieContainer();
NameValueCollection headers = new NameValueCollection();
// postData could also be an object or anonymous type
NameValueCollection postData = new NameValueCollection();
// get overloads
RequestFactory.CreateGet(requestUri)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreateGet(requestUri, postData)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreateGet(requestUri, postData, cookies)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreateGet(requestUri, postData, cookies, headers)
.GetResponse().GetResponseStream().Text();
}
/// <summary>
/// Creates a request using http 'Get' and ContentType.ApplicationJson. PostData is Url-encoded and appended to the Uri.
/// </summary>
[Test]
public void CreateGetJsonApp_Overloads()
{
Uri requestUri = NormalizeUri("Default.aspx");
CookieContainer cookies = new CookieContainer();
NameValueCollection headers = new NameValueCollection();
// postData could also be an object or anonymous type
NameValueCollection postData = new NameValueCollection();
RequestFactory.CreateGetJsonApp(requestUri)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreateGetJsonApp(requestUri, postData)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreateGetJsonApp(requestUri, postData, cookies)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreateGetJsonApp(requestUri, postData, cookies, headers)
.GetResponse().GetResponseStream().Text();
}
/// <summary>
/// Creates a request using http 'Get' and ContentType.TextJson. PostData is Url-encoded and appended to the Uri.
/// </summary>
[Test]
public void CreateGetJsonText_Overloads()
{
Uri requestUri = NormalizeUri("Default.aspx");
CookieContainer cookies = new CookieContainer();
NameValueCollection headers = new NameValueCollection();
// postData could also be an object or anonymous type
NameValueCollection postData = new NameValueCollection();
RequestFactory.CreateGetJsonText(requestUri)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreateGetJsonText(requestUri, postData)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreateGetJsonText(requestUri, postData, cookies)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreateGetJsonText(requestUri, postData, cookies, headers)
.GetResponse().GetResponseStream().Text();
}
/// <summary>
/// Creates a request using http 'Post' and ContentType.ApplicationForm. PostData is Url-encoded and written to the request body.
/// </summary>
[Test]
public void CreatePostForm_Overloads()
{
Uri requestUri = NormalizeUri("Default.aspx");
CookieContainer cookies = new CookieContainer();
NameValueCollection headers = new NameValueCollection();
// postData could also be an object or anonymous type
NameValueCollection postData = new NameValueCollection();
RequestFactory.CreatePostForm(requestUri)
.GetResponse().GetResponseStream().Text(); ;
RequestFactory.CreatePostForm(requestUri, postData)
.GetResponse().GetResponseStream().Text(); ;
RequestFactory.CreatePostForm(requestUri, postData, cookies)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreatePostForm(requestUri, postData, cookies, headers)
.GetResponse().GetResponseStream().Text();
}
/// <summary>
/// Creates a request using http 'Post' and ContentType.ApplicationJson. PostData is JSON-encoded and written to the request body.
/// This overload is most appropriate for posting to MS JSON endpoints an the rest of the functional examples will use
/// this overload.
/// </summary>
[Test]
public void CreatePostJsonApp_Overloads()
{
Uri requestUriNoArgs = NormalizeUri("AjaxService.svc/Noop");
Uri requestUri = NormalizeUri("AjaxService.svc/PutSessionVar");
CookieContainer cookies = new CookieContainer();
NameValueCollection headers = new NameValueCollection();
// postData could also be an object or a NameValueCollection
object postData = new { input = "foo" };
RequestFactory.CreatePostJsonApp(requestUriNoArgs)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreatePostJsonApp(requestUri, postData)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreatePostJsonApp(requestUri, postData, cookies)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreatePostJsonApp(requestUri, postData, cookies, headers)
.GetResponse().GetResponseStream().Text();
}
/// <summary>
/// Creates a request using http 'Post' and ContentType.TextJson. PostData is JSON-encoded and written to the request body.
/// </summary>
[Test]
public void CreatePostJsonText_Overloads()
{
Uri requestUriNoArgs = NormalizeUri("AjaxService.svc/Noop");
Uri requestUri = NormalizeUri("AjaxService.svc/PutSessionVar");
CookieContainer cookies = new CookieContainer();
NameValueCollection headers = new NameValueCollection();
// postData could also be an object or a NameValueCollection
object postData = new { input = "foo" };
RequestFactory.CreatePostJsonText(requestUriNoArgs)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreatePostJsonText(requestUri, postData)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreatePostJsonApp(requestUri, postData, cookies)
.GetResponse().GetResponseStream().Text();
RequestFactory.CreatePostJsonText(requestUri, postData, cookies, headers)
.GetResponse().GetResponseStream().Text();
}
使用 CreatePostJsonApp 处理 JSON 端点
所有重载都解析到基础 .CreateRequest()
方法,因此遵循相同的模式。本教程的重点是测试 MS JSON 端点,因此我们将专注于 .CreatePostJsonApp()
方法。
大多数其他场景,例如发布到标准表单和消耗非 MS REST 服务,都可以使用其他重载以类似的方式处理。
因此,使用 CreatePostJsonApp()
可以模拟用于消耗 ms JSON 端点的 XMLHttpRequest
。
在此示例中,为了演示三种 JSON 端点之间的直接对等性,我们创建了 3 个端点:一个 Ajax 启用的 WCF 服务、一个用 [ScriptService] 装饰的 WebServices,以及一个用 [WebMethod] (PageMethods) 装饰的静态 .aspx 方法,每个端点都有类似的 API。
我们将使用相同的代码在循环中调用其中每个端点,并观察相同的行为。
注意:在之前的列表中,已经展示了各种通用实用方法和内联工厂策略。您可能会发现这些模式很有用,但为了清晰起见,本教程的其余部分将使用展开的语法。
[Test]
public void Using_RequestFactory_CreatePostJsonApp_With_Json_Endpoints()
{
JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
// the methods will be calling looks like this:
// public string PutSessionVar(string input)
// public Result GetSessionVar()
// parameters can be defined with anonymous types.
var postData = new { input = "foo" };
// we will simply call API equivalent instances of each endpoint with the same code.
string[] jsonEndpoints = new[] { "AjaxService.svc", "ScriptService.asmx", "Default.aspx" };
foreach (string address in jsonEndpoints)
{
try
{
using (var response = RequestFactory.CreatePostJsonApp(NormalizeUri(address + "/PutSessionVar"), postData).GetResponse().GetResponseStream())
{
Result result = jsSerializer.CleanAndDeserialize(response.Text());
Assert.AreEqual("foo", result.Value);
}
// now lets get the value
try // this try/catch is a bit of a spoiler but bear with me
{
RequestFactory.CreatePostJsonApp(NormalizeUri(address + "/GetSessionVar")).GetResponse().GetResponseStream();
Assert.Fail("Expected WebException");
}
catch (WebException ex)
{
// as you can see, the value was not found in the session because we don't have a cookie
// container to share between requests
WebRequestException ex2 = WebRequestException.Create(ex);
Console.WriteLine("Caught {0}: {1}", ex2.ExceptionDetail.ExceptionType, ex2.Message);
}
}
catch (Exception ex)
{
Assert.Fail("Endpoint {0} failed with {1}", address, ex.Message);
}
}
// Which brings us to the overload of .CreatePostJsonApp that accepts a CookieContainer as an argument....
}
为了在请求之间维护会话状态、FormsAuthentication 和其他 Cookie,我们需要将一个通用的 CookieContainer
传递给每个请求。
还可以使用另一个重载将 Http 标头添加到请求中,但这在此处不进行处理。那些需要这样做的人将很容易理解实现。
[Test]
public void Using_CookieContainer_With_CreatePostJsonApp()
{
JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
string[] jsonEndpoints = new[] { "AjaxService.svc", "ScriptService.asmx", "Default.aspx" };
List<string> failures = new List<string>();
foreach (string address in jsonEndpoints)
{
try
{
// a container to store our cookies
CookieContainer cookies = new CookieContainer();
// the SessionId to verify cookies/session are working as expected
string sessionId;
// pass the CookieContainer in to catch any cookies set by the server
using (var response = RequestFactory.CreatePostJsonApp(NormalizeUri(address + "/PutSessionVar"), new { input = "foo" }, cookies).GetResponse().GetResponseStream())
{
Result result = jsSerializer.CleanAndDeserialize<Result>(response.Text());
Assert.AreEqual("foo", result.Value);
sessionId = result.Session;
}
// get the var
// pass the same CookieContainer in with the next request as it now contains the session cookie
using (var response = RequestFactory.CreatePostJsonApp(NormalizeUri(address + "/GetSessionVar"), null, cookies).GetResponse().GetResponseStream())
{
Result result = jsSerializer.CleanAndDeserialize<Result>(response.Text());
Assert.AreEqual("foo", result.Value);
Assert.AreEqual(sessionId, result.Session);
}
}
catch (Exception ex)
{
failures.Add(string.Format("Endpoint {0} failed {1} with {2}", address, "CreatePostJsonApp", ex.Message));
}
}
if (failures.Count > 0)
{
Assert.Fail(string.Join("\r\n", failures.ToArray()));
}
}
使用 CreateFilePost 上传流
主要的 CreateFilePost
方法将一个 Stream
上传到 Http 表单处理程序。
参数
requestUri - string 或 Uri
资源的绝对 URI
postData - NameValueCollection
可选。一个包含要与文件数据一起发布的表单字段的 NameValueCollection
fileData - Stream
一个包含文件数据的已打开、已定位的流。
fileName - string
要分配给文件数据并从中推断文件内容的名称(如果需要)。
fileContentType - string
可选。如果省略,则使用 fileName 查询注册表。如果从注册表中找不到内容类型,则提交 application/octet-stream。
fileFieldName - string
可选。用于表示提供数据的输入元素的名称的标识符。如果省略,则提交值 file。
cookies - CookieCollection
可选。在请求之间共享 CookieCollection 对于维护会话状态、FormsAuthentication 票证和其他 Cookie 是必需的。
headers - NameValueCollection
可选。要添加到请求的 Http 标头。
[Test]
public void Using_CreateFilePost_To_Upload_Data_From_Memory()
{
JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
var uploadResultPrototype = new
{
postData = default(string),
fileFieldName = default(string),
fileName = default(string),
fileContentType = default(string),
fileContentLength = default(int),
};
Uri requestUri = NormalizeUri("UploadHandler.ashx");
NameValueCollection postData = new NameValueCollection
{
{"field1", "field1Value"},
{"chkBoxGrp1", "a"},
{"chkBoxGrp1", "b"}
};
const string content = "some text";
Stream fileData = new MemoryStream(Encoding.UTF8.GetBytes(content));
const string fileName = "TextFileFromMemory.txt";
const string fileContentType = "text/plain";
const string fileFieldName = "fileField";
// am glossing over the use of cookies and headers as this has already been covered.
HttpWebRequest request = RequestFactory.CreateFilePost(requestUri, postData, fileData, fileName, fileContentType, fileFieldName, null, null);
try
{
using (Stream stream = request.GetResponse().GetResponseStream())
{
var response = jsSerializer.CleanAndDeserialize(stream.Text(), uploadResultPrototype);
Assert.AreEqual(9, response.fileContentLength);
Assert.AreEqual("text/plain", response.fileContentType);
Assert.AreEqual("fileField", response.fileFieldName);
Assert.AreEqual("TextFileFromMemory.txt", response.fileName);
Assert.AreEqual("field1=field1Value\r\nchkBoxGrp1=a,b\r\n", response.postData);
}
}
catch (WebException ex)
{
throw WebRequestException.Create(ex);
}
// just send data and filename
fileData.Position = 0;
HttpWebRequest request2 = RequestFactory.CreateFilePost(requestUri, fileData, fileName);
try
{
using (Stream stream = request2.GetResponse().GetResponseStream())
{
var response = jsSerializer.CleanAndDeserialize(stream.Text(), uploadResultPrototype);
Assert.AreEqual(9, response.fileContentLength);
Assert.AreEqual("text/plain", response.fileContentType);
Assert.AreEqual("file", response.fileFieldName);
Assert.AreEqual("TextFileFromMemory.txt", response.fileName);
}
}
catch (WebException ex)
{
throw WebRequestException.Create(ex);
}
}
使用 CreateFilePost 上传文件
第二个基础 CreateFilePost
方法接受一个物理文件路径,打开文件流并调用主 CreateFilePost
方法。
参数
requestUri - string 或 Uri
资源的绝对 URI
postData - NameValueCollection
可选。一个包含要与文件数据一起发布的表单字段的NameValueCollection
fileName - string
要上传的文件的物理路径
fileContentType - fileContentType - string
可选。如果省略,则使用fileName
查询注册表。如果从注册表中找不到内容类型,则提交 application/octet-stream。
fileFieldName - string
可选。用于表示提供数据的输入元素的名称的标识符。如果省略,则提交值 file。
cookies - CookieCollection
可选。在请求之间共享CookieCollection
对于维护会话状态、FormsAuthentication 票证和其他 Cookie 是必需的。
headers - NameValueCollection
可选。要添加到请求的 Http 标头。
[Test]
public void Using_CreateFilePost_To_Upload_File_From_Disk()
{
JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
var uploadResultPrototype = new
{
postData = default(string),
fileFieldName = default(string),
fileName = default(string),
fileContentType = default(string),
fileContentLength = default(int),
};
Uri requestUri = NormalizeUri("UploadHandler.ashx");
NameValueCollection postData = new NameValueCollection
{
{"field1", "field1Value"},
{"chkBoxGrp1", "a"},
{"chkBoxGrp1", "b"}
};
string fileName = Path.GetFullPath("TextFileFromDisk.txt");
const string fileContentType = "text/plain";
const string fileFieldName = "fileField";
var request = RequestFactory.CreateFilePost(requestUri, postData, fileName, fileContentType, fileFieldName, null, null);
try
{
using (Stream stream = request.GetResponse().GetResponseStream())
{
var response = jsSerializer.CleanAndDeserialize(stream.Text(), uploadResultPrototype);
Assert.AreEqual(12, response.fileContentLength); // content length is text length + BOM
Assert.AreEqual("text/plain", response.fileContentType);
Assert.AreEqual("fileField", response.fileFieldName);
Assert.AreEqual("TextFileFromDisk.txt", response.fileName);
Assert.AreEqual("field1=field1Value\r\nchkBoxGrp1=a,b\r\n", response.postData);
}
}
catch (WebException ex)
{
throw WebRequestException.Create(ex);
}
// just send data and filename
HttpWebRequest request2 = RequestFactory.CreateFilePost(requestUri, fileName);
try
{
using (Stream stream = request2.GetResponse().GetResponseStream())
{
var response = jsSerializer.CleanAndDeserialize(stream.Text(), uploadResultPrototype);
Assert.AreEqual(12, response.fileContentLength);// content length is text length + BOM
Assert.AreEqual("text/plain", response.fileContentType);
Assert.AreEqual("file", response.fileFieldName);
Assert.AreEqual("TextFileFromDisk.txt", response.fileName);
}
}
catch (WebException ex)
{
throw WebRequestException.Create(ex);
}
}
结论
通过上面提供的信息和模式,您现在应该能够快速轻松地为您的项目添加测试,以证明您的端点运行正常。
在主 HttpLib
测试中,您将找到对库中每个类和方法的更全面的介绍。
最新源代码和测试可在 http://salient.codeplex.com 找到
历史
- 2010/04/15 - 初始帖子