12

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.

fractal
  • 1,649
  • 17
  • 31

1 Answers1

19

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.

mythz
  • 141,670
  • 29
  • 246
  • 390
  • great stuff once again but got the error when registering sqlite in the container: Method 'GetLastInsertId' in type 'ServiceStack.OrmLite.Sqlite.SqliteOrmLiteDialectProvider' from assembly 'ServiceStack.OrmLite.SqliteNET, Version=3.9.60.0, Culture=neutral, PublicKeyToken=null' does not have an implementation. – fractal Sep 25 '13 at 19:32
  • @user904538 Try again with the latest version on NuGet (v3.9.63). Otherwise I recommend trying the [ServiceStack.OrmLite.Sqlite.Mono](https://www.nuget.org/packages/ServiceStack.OrmLite.Sqlite.Mono/) package - I prefer it because it also works in Mono you just need to set the `Build > Platform` to **x86**. – mythz Sep 25 '13 at 21:01
  • Followed instructions but 'Db' is null in my Service when I unit test the Service? Why is it not being injected as it does when running live? (Although its injected into experimental classes that have 'IDbConnectionFactory' in the constructor) – fractal Sep 26 '13 at 22:12
  • @user904538 have you set the resolver? `service.SetResolver(new BasicResolver(container));` – mythz Sep 26 '13 at 22:14
  • @user904538 wait, what Constructor? `Db` is a base property of the Service base class that gets [resolved from the injected IResolver](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack/Service.cs#L82) you shouldn't be overloading it or trying to set it yourself - please provide the full code of your class and point to what doesn't work. A [stand-alone gist is preferred](https://gist.github.com/). – mythz Sep 27 '13 at 02:02
  • I'm having the same issue. I followed the code sample above and Db is Null in my service.c I made sure I have the service.SetResolver code. Was there an update to ServiceStack that may have broken setting the resolver in this way without an AppHost? – Brett Mathe Jul 27 '16 at 22:25
  • @BrettMathe ServiceStack now [resolves Db from the overridable AppHost.GetDbConnection()](https://github.com/ServiceStack/ServiceStack/blob/master/docs/2016/v4.0.54.md#improved-support-for-multitenancy) to allow support for Multitenancy, it still falls back to resolving it from the AppHost IOC, but you'll need to setup a Basic AppHost as seen in the updated answer and [Testing wiki](https://github.com/ServiceStack/ServiceStack/wiki/Testing#test-setup). – mythz Jul 27 '16 at 23:12
  • When I follow that example I get no such table: Inventory_Item. The only difference between my code and the example is that I use the [Schema] attribute on my DTO classes because my tables are in different schemas. I can read the data out no problem in the setup code and I printed the captured SQL and I can see that the tables are getting created with the schema prefixed to the table name (e.g. Schema_Table). But when the service code runs and I call Db.Single(...) it fails saying it can't find the table. I am going to create the text example you linked to see if I can get that to work. – Brett Mathe Jul 28 '16 at 00:01
  • Something is wrong with my code :) I created the sample solution with the test code you linked and it works fine. I'll track it down and post what the problem was. I even added a Schema to Rockstar and it still worked. – Brett Mathe Jul 28 '16 at 00:15
  • 1
    I added a CapturedSqlFilter to see what SQL was generated when creating the tables. This was the problem. When I removed the CapturedSqlFilter the service behaved properly. – Brett Mathe Jul 28 '16 at 01:33