如何对ServiceStack进行单元测试? [英] How to unit test ServiceStack?
问题描述
我喜欢 SS,但我正在挠头尝试对我的业务层进行单元测试.我是单元测试和模拟的新手,一直在阅读 NSubstitute,因为它看起来像一个有趣的模拟层.
我的文件结构大致如下:
MainAppHostProject*|-AppStart-AppHost <-- 标准应用程序主机DtoProject*|-HelloWorldDto <-- 简单的 POCO 到服务层项目*|-HelloWorldService <-- 仅向/从业务层传递/发送 Dtos 的服务接口业务层项目*|-HelloWorldManager <-- 构建响应的逻辑,这个类扩展了服务"(让我访问 Db、会话等)...旁注:也许我应该把它称为 HelloWorldRepository?-CustomAuthProvider-自定义用户会话道项目*|-HelloWorldDao <-- 表结构的POCO
Apphost 指向 HelloWorldService 程序集并将 SQL Server 数据库注册为标准.
实际上一切都很好,我已经能够以更简洁的方式构建逻辑.不幸的是,我想开始单元测试,但我不知道如何解耦数据库.
我试图在内存数据库中注册一个假的,但后来我认为我在 SQL Server 与 SQLite 方式中使用代码获取身份等的方式存在不兼容问题.
//container.Register(c => new OrmLiteConnectionFactory(":memory:", false, SqliteOrmLiteDialectProvider.Instance));//container.Register(c => new OrmLiteConnectionFactory(":memory:", false, SqlServerDialect.Provider));
我只想解耦和单元测试.请问有什么想法吗?
***更新
公共类UnitTest1{私有容器容器;[测试方法]public void TestMethod1(){容器 = 新容器();//container.Register(new OrmLiteConnectionFactory(":memory:", false, SqliteDialect.Provider));//sqlite 不起作用,所以现在尝试使用真正的数据库var connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=XXX;Integrated Security=True";container.Register(c => new OrmLiteConnectionFactory(connectionString, SqlServerDialect.Provider));//依赖注入正常container.RegisterAutoWiredAs();//服务正在自动装配 -->导致良好的注射container.RegisterAutoWired();var service = container.Resolve();service.SetResolver(new BasicResolver(container));//单元测试运行良好var request = new DTO.FeedbackDto { Message = "test" };bool 结果 = service.Post(request);}}
目前试图让Db"在我的派生服务类中不再为空.
如果您想单独对 ServiceStack 服务进行单元测试,您可以采用几种不同的方法.基础 Service 类本身只是一个简单的 C# 类它允许您手动或使用内置 IOC 容器定义和注入依赖项.
我们将使用这个 简单的单元测试示例 测试这个简单的服务:
DTO
公共类 FindRockstars{公众号?岁{得到;放;}公共布尔?活着{得到;放;}}公共类 GetStatus{公共字符串姓氏 { 获取;放;}}公开课 RockstarStatus{公共整数年龄{得到;放;}公共布尔活着{得到;放;}}公开课摇滚明星{公共 int Id { 获取;放;}公共字符串名字{获取;放;}公共字符串姓氏 { 获取;放;}公众号?年龄{得到;放;}}
实施
公共类 SimpleService : 服务{公共 IRockstarRepository RockstarRepository { 获取;放;}公开名单<Rockstar>获取(FindRockstars 请求){返回 request.Aged.HasValue?Db.Select(q => q.Age == request.Aged.Value): Db.Select();}公共 RockstarStatus Get(GetStatus 请求){var rockstar = RockstarRepository.GetByLastName(request.LastName);如果(摇滚明星 == 空)throw HttpError.NotFound("'{0}' is not a Rockstar".Fmt(request.LastName));var status = 新的 RockstarStatus{活着 = RockstarRepository.IsAlive(request.LastName)}.PopulateWith(摇滚明星);//填充匹配的字段返回状态;}}
此服务提供 2 个操作,FindRockstars
直接在服务类中进行数据库查询,GetStatus
使用存储库代替所有数据访问.>
使用内存数据库
如果您直接从您的服务实现中访问 Db
,那么您将希望使用给定 ADO.NET IDbConnection 需要大量的工作来模拟.您可以通过使用内置 IOC 以与在 ServiceStack 本身中注册依赖项相同的方式执行此操作.对于单元测试,我们可以在没有 AppHost 的情况下执行此操作,只需在 TestFixtureSetup
中使用新的 Container
,例如:
测试设置
private ServiceStackHost appHost;[测试夹具设置]public void TestFixtureSetUp(){appHost = new BasicAppHost().Init();var 容器 = appHost.Container;container.Register(new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider));container.RegisterAutoWiredAs();container.RegisterAutoWired();使用 (var db = container.Resolve().Open()){db.DropAndCreateTable();db.InsertAll(SeedData);}}[TestFixtureTearDown]public void TestFixtureTearDown(){appHost.Dispose();}
完成所有设置后,我们现在可以像独立于 ServiceStack 本身的普通 C# 类一样测试服务:
[测试]public void Using_in_memory_database(){//从IOC解析自动装配的服务并为基类设置Resolvervar service = appHost.Container.Resolve();var Rockstars = service.Get(new FindRockstars { Aged = 27 });摇滚明星.PrintDump();//将结果的转储打印到控制台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 代替.
公共类 RockstarRepositoryMock : IRockstarRepository{公共摇滚明星 GetByLastName(string lastName){返回姓氏==维德"?新摇滚明星(6,埃迪",维德",48): 空值;}public bool IsAlive(string lastName){return lastName == "Grohl" ||姓 == "维德";}}[测试]public void Using_manual_dependency_injection(){var service = 新的 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 文档中.
I love SS but I'm scratching my head trying to unit test my business layer. I'm new to unit testing andmocking and been reading up on NSubstitute as this looks like a fun mocking layer.
I have my file structure roughly like this:
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
The Apphost points to the HelloWorldService assembly and registers the SQL Server database as standard.
Everything actually works great and I have been able to build up the logic in a cleaner way. Unfortunately I wish to embark on unit testing BUT I dont know how to decouple the database.
I tried to register a fake in memory database but then I think there's incompatibility issues with how I've used code to get identities etc in SQL Server vs SQLite ways.
// container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(":memory:", false, SqliteOrmLiteDialectProvider.Instance));
// container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(":memory:", false, SqlServerDialect.Provider));
I just want to decouple and unit test. Any ideas please?
***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);
}
}
At the moment trying to get 'Db' to stop being null in my derived Service classes.
If you want to unit test a ServiceStack Service in isolation there are a couple of different approaches you can take. The base Service class itself is just a simple C# class which lets you define and inject dependencies manually or by using the built-in IOC container.
We'll illustrate both approaches using this simple unit test example that tests this simple Service:
DTOs
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; }
}
Implementation
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;
}
}
This Service provides 2 operations, FindRockstars
which makes db queries directly in the service class itself, and GetStatus
which uses a repository instead for all its Data access.
Using an in-memory database
If you're accessing Db
from directly within your service implementation you're going to want to make use of a real DB given the ADO.NET IDbConnection requires a lot of effort to mock. You can do this in the same way you would register your dependencies in ServiceStack itself, by using the built-in IOC. For a unit test we can do this without an AppHost by just use a new Container
in your TestFixtureSetup
, e.g:
Test Setup
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();
}
With everything setup we can now test the service just like a normal C# class in isolation independently of ServiceStack itself:
[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" }));
}
Manually injecting dependencies
If you prefer your unit tests not to use an in-memory database, you can instead choose to mock your dependencies. In this example we'll use a stand-alone Mock, but you can reduce boilerplate by using mocking library like Moq instead.
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" }));
}
This example doesn't need a container as we're injecting all the dependencies manually. I've also added this example to the Testing wiki docs.
这篇关于如何对ServiceStack进行单元测试?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!