我喜欢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和SQLite方式中使用代码来获取身份等方面存在不兼容问题。
// container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(":memory:", false, SqliteOrmLiteDialectProvider.Instance));
// container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(":memory:", false, SqlServerDialect.Provider));
我只想解耦和单元测试。有什么想法吗?
*** UPDATE
public class UnitTest1
{
private Container container;
[TestMethod]
public void TestMethod1()
{
container = new Container();
// container.Register<IDbConnectionFactory>(new OrmLiteConnectionFactory(":memory:", false, 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'在派生的Service类中停止为null。
答案 0 :(得分:18)
如果您想单独测试ServiceStack服务,可以采用几种不同的方法。基类Service类本身只是一个简单的C#类,它允许您手动或使用内置IOC容器定义和注入依赖项。
我们将使用这个测试这个简单服务的simple unit test example来说明这两种方法:
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中注册依赖项相同的方式执行此操作。对于单元测试,我们可以在没有AppHost的情况下执行此操作,只需在Container
中使用新的TestFixtureSetup
,例如:
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.EqualTo(48));
Assert.That(status.Alive, Is.True);
Assert.Throws<HttpError>(() =>
service.Get(new GetStatus { LastName = "Hendrix" }));
}
此示例不需要容器,因为我们手动注入所有依赖项。我还将此示例添加到Testing wiki文档中。