Ответ 1
Если вы хотите unit test ServiceStack Service изолировать, вы можете принять несколько различных подходов. Базовый Service сам класс - это просто класс С#, который позволяет вам определять и вводить зависимости вручную или с помощью встроенного контейнера IOC.
Мы проиллюстрируем оба подхода, используя этот простой пример unit test, который проверяет эту простую службу:
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; }
}
Реализация
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
, которая делает запросы db непосредственно в самом классе службы, и GetStatus
, который использует репозиторий вместо всего доступа к данным.
Использование базы данных в памяти
Если вы обращаетесь к Db
непосредственно из своей реализации, вам захочется использовать реальную БД, учитывая ADO.NET IDbConnection требует много усилий для фальсификации. Вы можете сделать это точно так же, как вы регистрируете свои зависимости в самом ServiceStack, используя встроенный IOC. Для unit test мы можем сделать это без 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:
[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" }));
}
В этом примере не нужен контейнер, поскольку мы вводим все зависимости вручную. Я также добавил этот пример в Тестирование wiki docs.