One of the great things about using RavenDB and ServiceStack on Sonatribe is the ability to spin the whole system up inside a unit test. ServiceStack offers AppHostHttpListenerBase which can run in process and RavenDB offers the embeded database which can run in-memory. The fact that these two run in-process and in-memory means that regression testing Sonatribe REST services is simple and fast. Almost as fast as unit testing!
To be able to run tests like the following regression test:
I use the AppHostHttpListenerBase in my BaseTest class. Which is roughly the same as the following:
Now, When I inherit from SelfHostedBaseTest each test spins up a new host and a new in-memory RavenDB instance. This means I have a clean slate for each test, ideal for running everything in isolation. Not only this but I am also testing my services in the exact same stack as they run in production!
To be able to run tests like the following regression test:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Test] | |
public async void CanGetAccount() | |
{ | |
var user = await Client.PostAsync(new PostUserAccountRequest | |
{ | |
Name = UserName, | |
Slug = UserName, | |
Username = UserName, | |
ThirdPartyUsername = UserName | |
}); | |
var response = await Client.GetAsync(new GetUserAccountRequest | |
{ | |
Id = user.Results[0].Id | |
}); | |
response.Results[0].Id.Should().NotBeNull(); | |
} |
I use the AppHostHttpListenerBase in my BaseTest class. Which is roughly the same as the following:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Sonatribe.Tests.RegressionTests | |
{ | |
public class SelfHostedBaseTest | |
{ | |
protected IServiceClient Client; | |
protected AuthAppHostHttpListener AppHost; | |
protected virtual string ListeningOn | |
{ | |
get { return "http://localhost:82/"; } | |
} | |
protected virtual string WebHostUrl | |
{ | |
get { return "http://some.api.sonatribe.com"; } | |
} | |
[TestFixtureSetUp] | |
public void OnTestFixtureSetUp() | |
{ | |
AppHost = new AuthAppHostHttpListener(WebHostUrl, Configure); | |
AppHost.Init(); | |
AppHost.Start(ListeningOn); | |
//other set up stuff | |
} | |
[TestFixtureTearDown] | |
public void OnTestFixtureTearDown() | |
{ | |
AppHost.Dispose(); | |
} | |
protected IServiceClient GetClient() | |
{ | |
return new JsonServiceClient(ListeningOn) | |
{ | |
UserName = "xxxxx", | |
Password = "xxxxx" | |
}; | |
} | |
public virtual void Configure(Container container) | |
{ | |
container.Register<IServiceClient>(Client); | |
} | |
public class AuthAppHostHttpListener : AppHostHttpListenerBase | |
{ | |
private readonly string _webHostUrl; | |
private Action<Container> _configureFn; | |
public AuthAppHostHttpListener(string webHostUrl, Action<Container> configureFn = null) | |
: base("Validation Tests", typeof(OneOfYourServices).Assembly) | |
{ | |
_webHostUrl = webHostUrl; | |
_configureFn = configureFn; | |
} | |
public override void Configure(Funq.Container container) | |
{ | |
JsConfig.EmitCamelCaseNames = true; | |
ConfigureAuth(container); | |
ConfigureContainer(container); | |
InstallRaven(container); | |
} | |
public void InstallRaven(Funq.Container container) | |
{ | |
var store = new EmbeddableDocumentStore | |
{ | |
Configuration = | |
{ | |
RunInUnreliableYetFastModeThatIsNotSuitableForProduction = true, | |
RunInMemory = true | |
} | |
}.Initialize(); | |
//you can switch to a live/deployed API here if that tickles your fancy | |
//var store = new DocumentStore() | |
//{ | |
// ConnectionStringName = "SonatribeConnectionString" | |
//}.Initialize(); | |
store.Conventions.IdentityPartsSeparator = "-"; | |
container.Register<IDocumentStore>(store); | |
container.Register<IDocumentSession>(c => c.Resolve<IDocumentStore>().OpenSession()).ReusedWithin(ReuseScope.Request); | |
} | |
public static void DoInitialisation(IDocumentStore store) | |
{ | |
store.Initialize(); | |
store.Conventions.IdentityPartsSeparator = "-"; | |
store.Conventions.DefaultQueryingConsistency = ConsistencyOptions.AlwaysWaitForNonStaleResultsAsOfLastWrite; | |
//install transformers and indexes here | |
} | |
private void ConfigureContainer(Container container) | |
{ | |
container.Register(c => c.Resolve<IDocumentStore>().OpenSession()).ReusedWithin(ReuseScope.Default); | |
container.Register(c => c.Resolve<IDocumentStore>().OpenAsyncSession()).ReusedWithin(ReuseScope.Default); | |
//configure the dependancies that are needed by your services here | |
//this is generally the same set up as your actual service host | |
} | |
private void ConfigureAuth(Container container) | |
{ | |
var appSettings = new AppSettings(); | |
SetConfig(new HostConfig | |
{ | |
WebHostUrl = _webHostUrl, | |
StripApplicationVirtualPath = true, | |
DebugMode = true, | |
GlobalResponseHeaders = | |
{ | |
{"Access-Control-Allow-Origin", "*"}, | |
{"Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"}, | |
{"Access-Control-Allow-Headers", "Content-Type"}, | |
} | |
}); | |
Plugins.Add(new AuthFeature( | |
() => new CustomUserSession(), | |
new IAuthProvider[] | |
{ | |
new CredentialsAuthProvider(), | |
new TwitterAuthProvider(appSettings), | |
new FacebookAuthProvider(appSettings), | |
new DigestAuthProvider(appSettings), | |
new BasicAuthProvider() | |
}) { HtmlRedirect = null }); | |
container.Register<IAuthRepository>( | |
c => new RavenDbUserAuthRepository(container.Resolve<IDocumentStore>())); | |
} | |
} | |
} | |
} |
Now, When I inherit from SelfHostedBaseTest each test spins up a new host and a new in-memory RavenDB instance. This means I have a clean slate for each test, ideal for running everything in isolation. Not only this but I am also testing my services in the exact same stack as they run in production!
Comments
Post a Comment