如何对ServiceStack进行单元测试? [英] How to unit test ServiceStack?

查看:68
本文介绍了如何对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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆