我喜欢SS,但我正在抓我的头试图单元测试我的业务层.我是新来的单元测试和模拟,并正在阅读在NSubstitute,因为这看起来像一个有趣的嘲笑层.
我的文件结构大致如下:
MainAppHostProject* | -AppStart -AppHost <-- standard apphost DtoProject* | -HelloWorldDto <-- simple POCO to ServiceLayerProject* | -HelloWorldService <-- service interface that merely passes/sends Dtos to/from business layer BusinessLayerProject* | -HelloWorldManager <-- logic to construct response and this class extends 'Service' (letting me access Db,session,etc)...sidenote: maybe i shouldve called this a HelloWorldRepository? -CustomAuthProvider -CustomUserSession DaoProject* | -HelloWorldDao <-- POCO of table structure
Apphost指向HelloWorldService程序集,并以标准注册sql Server数据库.
一切真的很好,我已经能够以更清洁的方式建立逻辑.不幸的是,我希望进行单元测试,但我不知道如何解耦数据库.
我试图在内存数据库中注册一个假的,但是我认为在sql Server vs sqlite方式中,我已经使用代码来获取身份等等有不兼容的问题.
// container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(":memory:",false,sqliteOrmLiteDialectProvider.Instance)); // container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(":memory:",sqlServerDialect.Provider));
我只想去耦和单元测试.有什么想法吗?
*** UPDATE
public class UnitTest1 { private Container container; [TestMethod] public void TestMethod1() { container = new Container(); // container.Register<IDbConnectionFactory>(new OrmLiteConnectionFactory(":memory:",sqliteDialect.Provider)); // sqlite didnt work so attempting with a real DB for now var connectionString = @"Data Source=.\sqlEXPRESS;Initial Catalog=XXX;Integrated Security=True"; container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(connectionString,sqlServerDialect.Provider)); // dependencies are injecting ok container.RegisterAutoWiredAs<FeedbackRepo,IFeedbackRepo>(); // service is autowiring --> leading to good injections container.RegisterAutoWired<FeedbackService>(); var service = container.Resolve<FeedbackService>(); service.SetResolver(new BasicResolver(container)); // unit test is working well var request = new DTO.FeedbackDto { Message = "test" }; bool result = service.Post(request); } }
目前在我派生的服务类中,让’Db’停止为null.
解决方法
如果要单独测试ServiceStack服务,您可以采取几种不同的方法.基本
Service类本身只是一个简单的C#类,它可以手动定义和注入依赖关系,或者使用内置的IOC容器.
我们将使用这个测试这个简单服务的simple unit test example来说明这两种方法:
的DTO
public class FindRockstars { public int? Aged { get; set; } public bool? Alive { get; set; } } public class GetStatus { public string LastName { get; set; } } public class RockstarStatus { public int Age { get; set; } public bool Alive { get; set; } } public class Rockstar { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public int? Age { get; set; } }
履行
public class SimpleService : Service { public IRockstarRepository RockstarRepository { get; set; } public List<Rockstar> Get(FindRockstars request) { return request.Aged.HasValue ? Db.Select<Rockstar>(q => q.Age == request.Aged.Value) : Db.Select<Rockstar>(); } public RockstarStatus Get(GetStatus request) { var rockstar = RockstarRepository.GetByLastName(request.LastName); if (rockstar == null) throw HttpError.NotFound("'{0}' is not a Rockstar".Fmt(request.LastName)); var status = new RockstarStatus { Alive = RockstarRepository.IsAlive(request.LastName) }.PopulateWith(rockstar); //Populates with matching fields return status; } }
此服务提供2个操作,FindRockstars使得数据库查询直接在服务类本身中,GetStatus使用存储库而不是所有的数据访问.
使用内存数据库
如果您在服务实现中直接访问Db,那么您将要使用一个真正的数据库,因为ADO.NET IDbConnection需要大量的嘲弄.您可以通过使用内置的IOC以相同的方式在ServiceStack本身注册您的依赖关系.对于单元测试,只要在TestFixtureSetup中使用新的Container,我们就可以在没有AppHost的情况下执行此操作,例如:
测试设置
private ServiceStackHost appHost; [TestFixtureSetUp] public void TestFixtureSetUp() { appHost = new BasicAppHost().Init(); var container = appHost.Container; container.Register<IDbConnectionFactory>( new OrmLiteConnectionFactory(":memory:",sqliteDialect.Provider)); container.RegisterAutoWiredAs<RockstarRepository,IRockstarRepository>(); container.RegisterAutoWired<SimpleService>(); using (var db = container.Resolve<IDbConnectionFactory>().Open()) { db.DropAndCreateTable<Rockstar>(); db.InsertAll(SeedData); } } [TestFixtureTearDown] public void TestFixtureTearDown() { appHost.Dispose(); }
随着所有设置,我们现在可以独立于ServiceStack本身,隔离测试服务就像一个普通的C#类:
[Test] public void Using_in_memory_database() { //Resolve the autowired service from IOC and set Resolver for the base class var service = appHost.Container.Resolve<SimpleService>(); var rockstars = service.Get(new FindRockstars { Aged = 27 }); rockstars.PrintDump(); //Print a dump of the results to Console Assert.That(rockstars.Count,Is.EqualTo(SeedData.Count(x => x.Age == 27))); var status = service.Get(new GetStatus { LastName = "Vedder" }); Assert.That(status.Age,Is.EqualTo(48)); Assert.That(status.Alive,Is.True); status = service.Get(new GetStatus { LastName = "Hendrix" }); Assert.That(status.Age,Is.EqualTo(27)); Assert.That(status.Alive,Is.False); Assert.Throws<HttpError>(() => service.Get(new GetStatus { LastName = "Unknown" })); }
手动注入依赖关系
如果您希望单元测试不使用内存数据库,您可以选择模拟您的依赖项.在这个例子中,我们将使用一个独立的Mock,但是可以使用像Moq这样的嘲笑库来减少样板.
public class RockstarRepositoryMock : IRockstarRepository { public Rockstar GetByLastName(string lastName) { return lastName == "Vedder" ? new Rockstar(6,"Eddie","Vedder",48) : null; } public bool IsAlive(string lastName) { return lastName == "Grohl" || lastName == "Vedder"; } } [Test] public void Using_manual_dependency_injection() { var service = new SimpleService { RockstarRepository = new RockstarRepositoryMock() }; var status = service.Get(new GetStatus { LastName = "Vedder" }); Assert.That(status.Age,Is.True); Assert.Throws<HttpError>(() => service.Get(new GetStatus { LastName = "Hendrix" })); }
当我们手动注入所有依赖项时,此示例不需要容器.我也把这个例子添加到了Testing wiki文档中.