.net core 如何在.NETCore中编写真正的集成测试?
我有两个问题,但让我从头开始。我有ASP.NET Core 3.1 WebAPI,我正在寻找端到端测试控制器的最佳方法(我不想使用.net core 如何在.NETCore中编写真正的集成测试?,.net-core,entity-framework-core,tdd,integration-testing,asp.net-core-webapi,.net Core,Entity Framework Core,Tdd,Integration Testing,Asp.net Core Webapi,我有两个问题,但让我从头开始。我有ASP.NET Core 3.1 WebAPI,我正在寻找端到端测试控制器的最佳方法(我不想使用InMemoryprovider)。我将测试分为两组:“只读”(GET)和“读写”(POST,PUT,PATCH,DELETE)。对于只读测试,我希望创建一个全新的数据库(我使用代码优先迁移),然后逐个测试所有GET请求。对于读写请求,我希望创建新的数据库,并在测试后删除它 这就是我到目前为止所做的: public class TestFixture<TStar
InMemory
provider)。我将测试分为两组:“只读”(GET
)和“读写”(POST
,PUT
,PATCH
,DELETE
)。对于只读测试,我希望创建一个全新的数据库(我使用代码优先迁移),然后逐个测试所有GET
请求。对于读写请求,我希望创建新的数据库,并在测试后删除它
这就是我到目前为止所做的:
public class TestFixture<TStartup> : IDisposable where TStartup : class
{
private readonly TestServer _server;
public TestFixture()
{
var builder = new WebHostBuilder().UseStartup<TStartup>();
builder.ConfigureAppConfiguration((context, conf) =>
{
conf.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly);
});
_server = new TestServer(builder);
Client = _server.CreateClient();
Client.BaseAddress = new Uri("http://localhost:5000");
}
public HttpClient Client { get; }
public void Dispose()
{
Client.Dispose();
_server.Dispose();
}
}
公共类TestFixture:IDisposable其中TStartup:class
{
专用只读测试服务器(TestServer);;
公共测试设备()
{
var builder=new WebHostBuilder().UseStartup();
builder.ConfigureAppConfiguration((上下文,conf)=>
{
conf.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly);
});
_服务器=新的测试服务器(生成器);
Client=_server.CreateClient();
Client.BaseAddress=新Uri(“http://localhost:5000");
}
公共HttpClient客户端{get;}
公共空间处置()
{
Client.Dispose();
_Dispose();
}
}
。。。以及测试:
public class TestControllerShould : IClassFixture<TestFixture<Startup>>
{
public HttpClient Client { get; }
public TestControllerShould(TestFixture<Startup> fixture)
{
Client = fixture.Client;
}
[Fact]
public async Task GetHelloWorld()
{
// Arrange
var request = new HttpRequestMessage(new HttpMethod("GET"), "/test/");
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal("Hello World!", content);
}
}
公共类TestControllerShould:IClassFixture
{
公共HttpClient客户端{get;}
公共测试控制器(测试夹具)
{
Client=fixture.Client;
}
[事实]
公共异步任务GetHelloWorld()
{
//安排
var request=newhttprequestmessage(newhttpmethod(“GET”),“/test/”;
//表演
var response=wait Client.sendaync(请求);
//断言
Assert.Equal(HttpStatusCode.OK,response.StatusCode);
var content=await response.content.ReadAsStringAsync();
Assert.Equal(“helloworld!”,content);
}
}
我有两个问题。首先,我使用生产连接字符串从主项目启动(这是需要的,因为我想测试它),所以这是一个问题。第二个问题,我想在每次“写”测试之后创建并删除数据库,但由于明显的原因,我不能这样做。因此,我的问题是:
Startup
类,但仅将数据库名称更改为“MyDatabaseName+Guid.NewGuid()”
InMemory
提供程序。我也不想在测试结束时使用事务和回滚。我想做真正的集成测试。如果我正确理解了ASP.NET Core 3.1的功能,我们现在需要使用WebApplicationFactory
您可以覆盖其ConfigureWebHost
方法来更改配置、依赖项等。我最近用一个假的数据库实现替换了一个真实的数据库实现:
public class RestaurantApiFactory : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
if (builder is null)
throw new ArgumentNullException(nameof(builder));
builder.ConfigureServices(services =>
{
var descriptors = services
.Where(d =>
d.ServiceType == typeof(IReservationsRepository))
.ToList();
foreach (var d in descriptors)
services.Remove(d);
services.AddSingleton<IReservationsRepository>(
new FakeDatabase());
});
}
}
看起来您正在使用xUnit.net。如果是这样,您可以使用其BeforeAfterestAttribute
为每个测试创建和删除数据库
我通常是这样做的:
public class UseDatabaseAttribute : BeforeAfterTestAttribute
{
public override void Before(MethodInfo methodUnderTest)
{
using (var schemaStream = ReadSchema())
using (var rdr = new StreamReader(schemaStream))
{
var schemaSql = rdr.ReadToEnd();
var builder = new SqlConnectionStringBuilder(
ConnectionStrings.Reservations);
builder.InitialCatalog = "Master";
using (var conn = new SqlConnection(builder.ConnectionString))
using (var cmd = new SqlCommand())
{
conn.Open();
cmd.Connection = conn;
foreach (var sql in SeperateStatements(schemaSql))
{
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}
}
}
base.Before(methodUnderTest);
}
private Stream ReadSchema()
{
return typeof(SqlReservationsProgramVisitor<>)
.Assembly
.GetManifestResourceStream(
"Ploeh.Samples.BookingApi.Sql.BookingDbSchema.sql");
}
private static IEnumerable<string> SeperateStatements(string schemaSql)
{
return schemaSql.Split(
new[] { "GO" },
StringSplitOptions.RemoveEmptyEntries);
}
public override void After(MethodInfo methodUnderTest)
{
base.After(methodUnderTest);
var dropCmd = @"
IF EXISTS (SELECT name
FROM master.dbo.sysdatabases
WHERE name = N'Booking')
DROP DATABASE[Booking];";
var builder = new SqlConnectionStringBuilder(
ConnectionStrings.Reservations);
builder.InitialCatalog = "Master";
using (var conn = new SqlConnection(builder.ConnectionString))
using (var cmd = new SqlCommand(dropCmd, conn))
{
conn.Open();
cmd.ExecuteNonQuery();
}
}
}
公共类UseDatabaseAttribute:BeforeAfterTestAttribute
{
公共覆盖之前无效(MethodInfo methodUnderTest)
{
使用(var schemaStream=ReadSchema())
使用(var rdr=newstreamreader(schemaStream))
{
var schemaSql=rdr.ReadToEnd();
var builder=new-SqlConnectionStringBuilder(
连接字符串(保留);
builder.InitialCatalog=“Master”;
使用(var conn=new SqlConnection(builder.ConnectionString))
使用(var cmd=new SqlCommand())
{
conn.Open();
cmd.Connection=conn;
foreach(单独语句中的var-sql(schemaSql))
{
cmd.CommandText=sql;
cmd.ExecuteNonQuery();
}
}
}
基础。之前(测试中的方法);
}
私有流ReadSchema()
{
返回类型(SqlReservationsProgramVisitor)
装配
.getResourceStream(
“Ploeh.Samples.BookingApi.Sql.BookingDbSchema.Sql”);
}
私有静态IEnumerable SeparateStatements(字符串模式SQL)
{
返回schemaSql.Split(
新[]{“GO”},
StringSplitOptions.RemoveEmptyEntries);
}
公共覆盖之后无效(MethodInfo methodUnderTest)
{
基础。之后(方法待测);
var dropCmd=@”
如果存在(请选择名称)
从master.dbo.sysdatabases
其中name=N‘预订’)
删除数据库[预订];“;
var builder=new-SqlConnectionStringBuilder(
连接字符串(保留);
builder.InitialCatalog=“Master”;
使用(var conn=new SqlConnection(builder.ConnectionString))
使用(var cmd=new SqlCommand(dropCmd,conn))
{
conn.Open();
cmd.ExecuteNonQuery();
}
}
}
您可以看到它正在使用。如果我正确理解了ASP.NET Core 3.1的用法,我们现在需要使用WebApplicationFactory
您可以覆盖其ConfigureWebHost
方法来更改配置、依赖项等。我最近用一个假的数据库实现替换了一个真实的数据库实现:
public class RestaurantApiFactory : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
if (builder is null)
throw new ArgumentNullException(nameof(builder));
builder.ConfigureServices(services =>
{
var descriptors = services
.Where(d =>
d.ServiceType == typeof(IReservationsRepository))
.ToList();
foreach (var d in descriptors)
services.Remove(d);
services.AddSingleton<IReservationsRepository>(
new FakeDatabase());
});
}
}
看起来您正在使用xUnit.net。如果是这样,您可以使用其BeforeAfterestAttribute
为每个测试创建和删除数据库
我通常是这样做的:
public class UseDatabaseAttribute : BeforeAfterTestAttribute
{
public override void Before(MethodInfo methodUnderTest)
{
using (var schemaStream = ReadSchema())
using (var rdr = new StreamReader(schemaStream))
{
var schemaSql = rdr.ReadToEnd();
var builder = new SqlConnectionStringBuilder(
ConnectionStrings.Reservations);
builder.InitialCatalog = "Master";
using (var conn = new SqlConnection(builder.ConnectionString))
using (var cmd = new SqlCommand())
{
conn.Open();
cmd.Connection = conn;
foreach (var sql in SeperateStatements(schemaSql))
{
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}
}
}
base.Before(methodUnderTest);
}
private Stream ReadSchema()
{
return typeof(SqlReservationsProgramVisitor<>)
.Assembly
.GetManifestResourceStream(
"Ploeh.Samples.BookingApi.Sql.BookingDbSchema.sql");
}
private static IEnumerable<string> SeperateStatements(string schemaSql)
{
return schemaSql.Split(
new[] { "GO" },
StringSplitOptions.RemoveEmptyEntries);
}
public override void After(MethodInfo methodUnderTest)
{
base.After(methodUnderTest);
var dropCmd = @"
IF EXISTS (SELECT name
FROM master.dbo.sysdatabases
WHERE name = N'Booking')
DROP DATABASE[Booking];";
var builder = new SqlConnectionStringBuilder(
ConnectionStrings.Reservations);
builder.InitialCatalog = "Master";
using (var conn = new SqlConnection(builder.ConnectionString))
using (var cmd = new SqlCommand(dropCmd, conn))
{
conn.Open();
cmd.ExecuteNonQuery();
}
}
}
公共类UseDatabaseAttribute:BeforeAfterTestAttribute
{
公共覆盖之前无效(MethodInfo methodUnderTest)
{
使用(var schemaStream=ReadSchema())