我想测试一些属于ASP.NET Web Api项目的类。我不需要通过TestServer进行请求 - 响应集成测试(虽然它们很好)但我想让我的测试尽可能接近“真实的东西”。所以我想使用Startup中添加的服务来解析我的类,但是在测试的基础上通过存根/模拟更改其中的一些(一些测试需要模拟,其他测试则不需要)。
在ASP.NET没有内部依赖注入框架的过去,很容易做到这一点。所以我只需要调用一个将所有依赖项注册到容器的类,然后为每个测试创建子容器,将一些依赖项更改为mocks,就是这样。
我试过这样的事情:
var host = A.Fake<IHostingEnvironment>();
var startup = new Startup(host);
var services = new ServiceCollection();
//Add stubs here
startup.ConfigureServices(services);
var provider = services.BuildServiceProvider();
provider.GetService<IClientsHandler>();
它似乎工作,但我不想为每个测试创建整个启动基础设施。我想创建它一次,然后为每个测试创建“子容器”或“子范围”。可能吗?基本上我正在寻找一种方法来修改Startup之外的服务。
如果你想为你的Web API Core
单元测试使用相同的DI
控制器和xUnit
基础设施(在这种情况下我称之为集成测试),我建议将TestServer
和HttpClient
上下文移动到实现xUnit
IClassFixture
的基类。
在这种情况下,你将测试API
所有的服务和DI
配置你真正的Web API Core
:
public class TestServerDependent : IClassFixture<TestServerFixture>
{
private readonly TestServerFixture _fixture;
public TestServer TestServer => _fixture.Server;
public HttpClient Client => _fixture.Client;
public TestServerDependent(TestServerFixture fixture)
{
_fixture = fixture;
}
protected TService GetService<TService>()
where TService : class
{
return _fixture.GetService<TService>();
}
}
public class TestServerFixture : IDisposable
{
public TestServer Server { get; }
public HttpClient Client { get; }
public TestServerFixture()
{
// UseStaticRegistration is needed to workaround AutoMapper double initialization. Remove if you don't use AutoMapper.
ServiceCollectionExtensions.UseStaticRegistration = false;
var hostBuilder = new WebHostBuilder()
.UseEnvironment("Testing")
.UseStartup<Startup>();
Server = new TestServer(hostBuilder);
Client = Server.CreateClient();
}
public void Dispose()
{
Server.Dispose();
Client.Dispose();
}
public TService GetService<TService>()
where TService : class
{
return Server?.Host?.Services?.GetService(typeof(TService)) as TService;
}
}
然后你可以从这个类派生来以这种方式测试控制器动作:
public class ValueControllerTests : TestServerDependent
{
public ValueControllerTests(TestServerFixture fixture)
: base(fixture)
{
}
[Fact]
public void Returns_Ok_Response_When_Requested()
{
var responseMessage = Client.GetAsync("/api/value").Result;
Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);
}
}
您还可以测试DI
服务:
public class MyServiceTests : TestServerDependent
{
public MyServiceTests(TestServerFixture fixture)
: base(fixture)
{
}
[Fact]
public void ReturnsDataWhenServiceInjected()
{
var service = GetService<IMyService>();
Assert.NotNull(service);
var data = service.GetData();
Assert.NotNull(data);
}
}
通过创建自定义IHttpControllerActivator
配置HttpConfiguration时,可以为每个请求创建子范围。
这适用于OWIN,但转换为.Net Core应该非常简单:https://gist.github.com/jt000/eef096a2341471856e8a86d06aaec887
重要的部分是在该范围内创建范围和控制器......
var scope = _provider.CreateScope();
request.RegisterForDispose(scope);
var controller = scope.ServiceProvider.GetService(controllerType) as IHttpController;
...并覆盖默认的IHttpControllerActivator
......
config.Services.Replace(typeof (IHttpControllerActivator), new ServiceProviderControllerActivator(parentActivator, provider));
现在,您可以通过带有作用域依赖注入的IServiceProvider添加要创建的控制器...
services.AddScoped<ValuesController>((sp) => new ValuesController(sp.GetService<ISomeCustomService>()));
要在单元测试中测试ValuesController,我建议使用类似Moq框架的东西来模拟服务接口中的方法。例如:
var someCustomService = Mock.Of<ISomeCustomService>(s => s.DoSomething() == 3);
var sut = new ValuesController(someCustomService);
var result = sut.Get();
Assert.AreEqual(result, new [] { 3 });
这一切都在documentation中得到了很好的报道。
对于集成测试,你使用TestServer
类并给它一个Startup
类(不必是现场启动,也可以是StartupIntegrationTest
或启动与Configure{Envrionment Name here}
/ ConfigureServices{Envrionment Name here}
方法。
var server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
// this would cause it to use StartupIntegrationTest class or ConfigureServicesIntegrationTest / ConfigureIntegrationTest methods (if existing)
// rather than Startup, ConfigureServices and Configure
.UseEnvironment("IntegrationTest"));
要访问服务提供商,请执行
var server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.UseEnvironment("IntegrationTest"));
var controller = server.Host.Services.GetService<MyService>();
对于单元测试,你根本不应该使用IServiceCollection
/ IServiceProvider
,只需模拟接口并注入它们。