现在我们在应用层中只有两个功能,增加诗人和诗人分页列表:
using Volo.Abp.Application.Services;
namespace ZL.AbpNext.Poem.Application.Poems
{
public interface IPoemAppService:IApplicationService
{
///
/// 获取诗人分页
///
///
///
PagedResultDto<PoetDto> GetPagedPoets(PagedResultRequestDto dto);
PoetDto AddPoet(PoetDto poet);
}
}
在继续开发之前,我们先搭建单元测试环境,对已经开发的部分进行单元测试。
首先我们在解决方案中创建一个“解决方案文件夹”,并命名为Test,这个文件夹下保存测试项目。Abp vNext采用xUnit作为测试框架,所以我们在这个文件夹中创建一个xUnit类型的测试项目,名称为ZL.AbpNext.Poem.Application.Test。然后在这个项目中增加项目依赖,还要使用NuGet添加volo.Abp.TestBase。所有的依赖关系如下:
单元测试时,我们不能使用真实的数据库,我们需要使用Sqlite内存数据库进行测试。Sqlite的内存数据库中的表和数据只在数据库连接有效时起作用,如果连接关闭,数据库中的数据就丢失了。因此,需要在测试模块中进行设置,每次连接时,都需要重新创建数据库表,代码如下:
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Text;
using Volo.Abp;
using Volo.Abp.Autofac;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Sqlite;
using Volo.Abp.Modularity;
using ZL.AbpNext.Poem.Core;
using ZL.AbpNext.Poem.EF;
using ZL.AbpNext.Poem.EF.EntityFramework;
namespace ZL.AbpNext.Poem.Application.Test
{
[DependsOn(
typeof(AbpAutofacModule),
typeof(PoemCoreModule),
typeof(PoemApplicationModule),
typeof(PoemDataModule),
typeof(AbpEntityFrameworkCoreSqliteModule))]
public class PoemApplicationTestModule : AbpModule
{
private SqliteConnection _sqliteConnection;
public override void ConfigureServices(ServiceConfigurationContext context)
{
ConfigureInMemorySqlite(context.Services);
}
private void ConfigureInMemorySqlite(IServiceCollection services)
{
_sqliteConnection = CreateDatabaseAndGetConnection();
services.Configure<AbpDbContextOptions>(options =>
{
options.Configure(context =>
{
context.DbContextOptions.UseSqlite(_sqliteConnection);
});
});
}
public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
_sqliteConnection.Dispose();
}
private static SqliteConnection CreateDatabaseAndGetConnection()
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<PoemDbContext>()
.UseSqlite(connection)
.Options;
using (var context = new PoemDbContext(options))
{
context.GetService<IRelationalDatabaseCreator>().CreateTables();
}
return connection;
}
}
}
接下来创建测试基类,所有的测试类都从这个类派生:
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.Modularity;
using Volo.Abp.Uow;
using Volo.Abp.Testing;
namespace ZL.AbpNext.Poem.Application.Test
{
public abstract class PoemTestBase<TStartupModule> : AbpIntegratedTest<TStartupModule>
where TStartupModule : IAbpModule
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
protected virtual Task WithUnitOfWorkAsync(Func<Task> func)
{
return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func);
}
protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func<Task> action)
{
using (var scope = ServiceProvider.CreateScope())
{
var uowManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
using (var uow = uowManager.Begin(options))
{
await action();
await uow.CompleteAsync();
}
}
}
protected virtual Task<TResult> WithUnitOfWorkAsync<TResult>(Func<Task<TResult>> func)
{
return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func);
}
protected virtual async Task<TResult> WithUnitOfWorkAsync<TResult>(AbpUnitOfWorkOptions options, Func<Task<TResult>> func)
{
using (var scope = ServiceProvider.CreateScope())
{
var uowManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
using (var uow = uowManager.Begin(options))
{
var result = await func();
await uow.CompleteAsync();
return result;
}
}
}
}
}
现在就可以编写正式的单元测试了:
using System;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Domain.Repositories;
using Xunit;
using ZL.AbpNext.Poem.Application.Poems;
using ZL.AbpNext.Poem.Core.Poems;
using ZL.AbpNext.Poem.EF.EntityFramework;
namespace ZL.AbpNext.Poem.Application.Test
{
public class UnitTest1:PoemTestBase<PoemApplicationTestModule>
{
private readonly IRepository<Poet, int> _appPoet;
private readonly IPoemAppService _service;
public UnitTest1()
{
_appPoet= GetRequiredService<IRepository<Poet, int>>();
_service = GetRequiredService<IPoemAppService>();
}
[Fact]
public async Task TestAddPoet()
{
await WithUnitOfWorkAsync(async () =>
{
var poet = new Poet
{
Name = "李白",
Description = "诗人"
};
//Act
var addedPoet = await _appPoet.InsertAsync(poet,true);
Assert.True(_appPoet.Count() ==1);
//Assert
Assert.True(addedPoet.Id>0);
});
}
[Fact]
public async Task TestAddWithServicet()
{
await WithUnitOfWorkAsync(async () =>
{
var poet = new PoetDto
{
Name = "李白",
Description = "诗人"
};
//Act
var addPoet = _service.AddPoet(poet);
Assert.True(addPoet.Id > 0);
var res = _service.GetPagedPoets(new Volo.Abp.Application.Dtos.PagedResultRequestDto {
MaxResultCount = 10, SkipCount = 0 });
Assert.True(res.TotalCount == 1);
});
}
}
}
前面我们搭建了单元测试的框架,在单元测试中使用Sqlite的内存数据库进行测试。在每个测试用例启动的时候,都需要重新创建数据库结构,我们需要向数据库中写入一些初始数据,用于测试,这里介绍写入初始数据的方法。在测试项目中,新增加一个类负责写入初始数据,名称为PoetTestDataSeed:
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using ZL.AbpNext.Poem.Core.Poems;
namespace ZL.AbpNext.Poem.Application.Test
{
public class PoetTestDataSeed : IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Poet, int> _appPoet;
public PoetTestDataSeed(IRepository<Poet, int> _appPoet)
{
this._appPoet = _appPoet;
}
public Task SeedAsync(DataSeedContext context)
{
_appPoet.InsertAsync(new Poet
{
Name = "李白",
Description = "诗人"
}, true);
_appPoet.InsertAsync(new Poet
{
Name = "杜甫",
Description = "诗人"
}, true);
return Task.CompletedTask;
}
}
}
然后,修改PoemApplicationTestModule,增加初始化写入测试数据的代码:
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
SeedTestData(context);
}
private static void SeedTestData(ApplicationInitializationContext context)
{
AsyncHelper.RunSync(async () =>
{
using (var scope = context.ServiceProvider.CreateScope())
{
await scope.ServiceProvider
.GetRequiredService<IDataSeeder>()
.SeedAsync();
}
});
}
这时再次运行测试,发现出现测试错误,这是因为数据库中的记录个数大于1,我们将原来的代码修改为:
Assert.True(_appPoet.Count() >1);
再次运行,测试通过了。