How to unit test ServiceStack?

后端 未结 1 716
伪装坚强ぢ
伪装坚强ぢ 2021-02-08 02:46

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 moc

相关标签:
1条回答
  • 2021-02-08 02:53

    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.

    0 讨论(0)
提交回复
热议问题