在 IHttpControllerActivator 中模拟 Do-Not-Cares
Mocking Do-Not-Cares in IHttpControllerActivator
我正在重构我的测试工具(SpecFlow 2.0.0 over NUnit 3.0.5797.27534)以驱动我的控制器进行特定测试。
Scenario: FluxCapacitor Works
Given I have entered '11-05-1955' into the Time Circuits
When I get the DeLorean up to 88 mph
Then the date on the newspaper should be '11-05-1955'
这是我 运行 用于测试架构的常见脚手架示例。
public class TimeController: ApiController
{
private TimeProvider TimeProvider{get;set;}
public TimeController(TimeProvider timeProvider)
{
this.TimeProvider = timeProvider;
}
}
[HttpGet]
[Route("Time;now")]
public DateTime GetCurrentTime()
{
return TimeProvider.Current.Now;
}
...所以,我正在尝试模拟 Then
调用...
[Then(@"the date on the newspaper should be '(.*)'")]
public void ThenTheDateOnTheNewspaperShouldBe(string p0)
{
DateTime expected = DateTime.Parse(p0);
Startup startup = new Startup();
Hosting.MySystemStartupOptions options = new Hosting.MySystemStartupOptions();
//+===================================================================+
//[KAB] This is the line that I think is of primary importance in this
options.Dispatcher = DelegateThatReturnsMyMockControllerActivator();
//+===================================================================+
startup.Register = () => { return options;};
//+===================================================================+
//[options.Dispatcher] gets pushed down to my WebApiConfiguration that
//executes [Services.Replace(typeof(IHttpControllerActivator),options.Dispatcher]
//+===================================================================+
using(WebApp.Start(url: "http://localhost:9000/", startup:startup.Configuration))
using(var client = new System.Net.Http.HttpClient())
using(System.Net.Http.HttpResponseMessage response = client.GetAsync(ResourceUnderTest.ToString()).Result)
{
if(response.StatusCode != System.Net.HttpStatusCode.OK)
{
Assert.Fail("you suck");
}
//never getting past, because I suck.
}
}
所以 returns 我的 IHttpControllerActivator
的代表实际上叫做 Composer
作曲家 returns 一个模拟。
Composer
这样看:
Composer = () =>
{
var request = new Moq.Mock<System.Net.Http.HttpRequestMessage>();
var descriptor = new Moq.Mock<System.Web.Http.Controllers.HttpControllerDescriptor>();
var dispatcher = new Moq.Mock<System.Web.Http.Dispatcher.IHttpControllerActivator>();
Type controllerType = typeof(resources.controllers.TimeController);
var provider = new Moq.Mock<TimeProvider>();
provider.Setup(time => time.Now).Returns(Variable); //<=Variable is initialized in the `Given`
TimeProvider timeProvider = provider.Object;
Service = new resources.controllers.TimeController(timeProvider: timeProvider);
//+======================================================================+
//I can't figure out if there is a way to have "all calls to Create for a
//specific controllerType, but I do not care about the request or the
//descriptor" returns [Service]
dispatcher.Setup(context => context.Create(request.Object, descriptor.Object, controllerType)).Returns(Service);
//+======================================================================+
dispatcher.As<IDisposable>().Setup(disposable => disposable.Dispose());
return dispatcher.Object;
}
我最初的想法是认为 TimeController 没有被模拟返回,因为 Create
的签名以及我对路由的执行没有提供与模拟相匹配的请求和描述符这一事实在激活器设置中提供。
因此,我将 Dispatcher 的创建替换为:
(请求和 controllerDescriptor 替换为 IsAny
检查)
dispatcher.Setup(context => context.Create(It.IsAny(), It.IsAny(), controllerType)).Returns(服务);
但我的请求没有到达定义的路线。
有人知道如何让我的模拟 TimeController 在调用 client.GetAsync
时返回吗?
最小起订量不是问题。在我的 Startup
中,我错误地将配置的属性路由隐藏在 IAppBulder.Map('NewRoute',...)
后面。¹ 修复了这些废话之后,我上面包含的代码可以正常工作。为了完整起见,以防有人在研究中偶然发现这个问题,我不得不更改我的客户端代码以使测试通过:
using(WebApp.Start(url: "http://localhost:9000/", startup: startup.Configuration))
using(var client = new System.Net.Http.HttpClient))
{
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
using(System.Net.Http.HttpResponseMessage response = client.GetAsync(ResourceUnderTest.ToString()).Result)
{
if(response.StatusCode != System.Net.HttpStatusCode.OK){Assert.Fail("you suck");}
String content = response.Content.ReadAsStringAsync().Result;
DateTime actual = Newtonsoft.Json.JsonConvert.DeserializeObject<DateTime>(token);
Assert.AreEqual(actual, expected);
}
}
► Passed Tests (1)
✔ FluxCapacitor
..它与原始代码相同,只是将 ("application/json") 设置为客户端的接受 header。
¹ ...I guess that my [start-stop],[start-stop],[start-stop] cycle to
deal with drive-by interruptions finally got to me yesterday. Since I
(obviously) didn't know exactly what the culprit was, I was concerned enough
that the problem was in my Mock that I wanted to raise the question to the
community before I left for the day. But walked away and looked at it again
with a new day's eyes and it took about 30 seconds to find the culprit.
我正在重构我的测试工具(SpecFlow 2.0.0 over NUnit 3.0.5797.27534)以驱动我的控制器进行特定测试。
Scenario: FluxCapacitor Works
Given I have entered '11-05-1955' into the Time Circuits
When I get the DeLorean up to 88 mph
Then the date on the newspaper should be '11-05-1955'
这是我 运行 用于测试架构的常见脚手架示例。
public class TimeController: ApiController
{
private TimeProvider TimeProvider{get;set;}
public TimeController(TimeProvider timeProvider)
{
this.TimeProvider = timeProvider;
}
}
[HttpGet]
[Route("Time;now")]
public DateTime GetCurrentTime()
{
return TimeProvider.Current.Now;
}
...所以,我正在尝试模拟 Then
调用...
[Then(@"the date on the newspaper should be '(.*)'")]
public void ThenTheDateOnTheNewspaperShouldBe(string p0)
{
DateTime expected = DateTime.Parse(p0);
Startup startup = new Startup();
Hosting.MySystemStartupOptions options = new Hosting.MySystemStartupOptions();
//+===================================================================+
//[KAB] This is the line that I think is of primary importance in this
options.Dispatcher = DelegateThatReturnsMyMockControllerActivator();
//+===================================================================+
startup.Register = () => { return options;};
//+===================================================================+
//[options.Dispatcher] gets pushed down to my WebApiConfiguration that
//executes [Services.Replace(typeof(IHttpControllerActivator),options.Dispatcher]
//+===================================================================+
using(WebApp.Start(url: "http://localhost:9000/", startup:startup.Configuration))
using(var client = new System.Net.Http.HttpClient())
using(System.Net.Http.HttpResponseMessage response = client.GetAsync(ResourceUnderTest.ToString()).Result)
{
if(response.StatusCode != System.Net.HttpStatusCode.OK)
{
Assert.Fail("you suck");
}
//never getting past, because I suck.
}
}
所以 returns 我的 IHttpControllerActivator
的代表实际上叫做 Composer
作曲家 returns 一个模拟。
Composer
这样看:
Composer = () =>
{
var request = new Moq.Mock<System.Net.Http.HttpRequestMessage>();
var descriptor = new Moq.Mock<System.Web.Http.Controllers.HttpControllerDescriptor>();
var dispatcher = new Moq.Mock<System.Web.Http.Dispatcher.IHttpControllerActivator>();
Type controllerType = typeof(resources.controllers.TimeController);
var provider = new Moq.Mock<TimeProvider>();
provider.Setup(time => time.Now).Returns(Variable); //<=Variable is initialized in the `Given`
TimeProvider timeProvider = provider.Object;
Service = new resources.controllers.TimeController(timeProvider: timeProvider);
//+======================================================================+
//I can't figure out if there is a way to have "all calls to Create for a
//specific controllerType, but I do not care about the request or the
//descriptor" returns [Service]
dispatcher.Setup(context => context.Create(request.Object, descriptor.Object, controllerType)).Returns(Service);
//+======================================================================+
dispatcher.As<IDisposable>().Setup(disposable => disposable.Dispose());
return dispatcher.Object;
}
我最初的想法是认为 TimeController 没有被模拟返回,因为 Create
的签名以及我对路由的执行没有提供与模拟相匹配的请求和描述符这一事实在激活器设置中提供。
因此,我将 Dispatcher 的创建替换为:
(请求和 controllerDescriptor 替换为 IsAny
检查)
dispatcher.Setup(context => context.Create(It.IsAny(), It.IsAny(), controllerType)).Returns(服务);
但我的请求没有到达定义的路线。
有人知道如何让我的模拟 TimeController 在调用 client.GetAsync
时返回吗?
最小起订量不是问题。在我的 Startup
中,我错误地将配置的属性路由隐藏在 IAppBulder.Map('NewRoute',...)
后面。¹ 修复了这些废话之后,我上面包含的代码可以正常工作。为了完整起见,以防有人在研究中偶然发现这个问题,我不得不更改我的客户端代码以使测试通过:
using(WebApp.Start(url: "http://localhost:9000/", startup: startup.Configuration))
using(var client = new System.Net.Http.HttpClient))
{
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
using(System.Net.Http.HttpResponseMessage response = client.GetAsync(ResourceUnderTest.ToString()).Result)
{
if(response.StatusCode != System.Net.HttpStatusCode.OK){Assert.Fail("you suck");}
String content = response.Content.ReadAsStringAsync().Result;
DateTime actual = Newtonsoft.Json.JsonConvert.DeserializeObject<DateTime>(token);
Assert.AreEqual(actual, expected);
}
}
► Passed Tests (1)
✔ FluxCapacitor
..它与原始代码相同,只是将 ("application/json") 设置为客户端的接受 header。
¹ ...I guess that my [start-stop],[start-stop],[start-stop] cycle to
deal with drive-by interruptions finally got to me yesterday. Since I
(obviously) didn't know exactly what the culprit was, I was concerned enough
that the problem was in my Mock that I wanted to raise the question to the
community before I left for the day. But walked away and looked at it again
with a new day's eyes and it took about 30 seconds to find the culprit.