
This is the second post in my series on the awesome testability of
Nancy - a
minimalistic web framework on the .NET / Mono platform. Let's throw in
the logo again - it so nice.
The other posts can be found here:
1.
Intro to testing with Nancy
2.
The Configurable bootstrapper(this post)
3.
The Browser and Response objects
4.
Hat and shoeless testing with Simple.Data
5.
SpecFlow and Nancy
This post covers a basic feature that makes up much of the awesomeness
that is around configurability in Nancy testing: the configurable
bootstrapper.
There's a
wiki-post on testing on the Nancy Github wiki but it
leaves the ConfigurableBootstrapper with a mere mentioning. I think it
deserves a deeper look, since you through this actually can control and
swap out most anything from the Nancy framework. Even ViewEngines and
the NancyEngine itself are possible to change to something that suits
you better. In the testing case - we can supply fakes and mocks.
Let's get to it - there's a lot of code to get through.
### The Configurator and how we use it
To call into a Nancy module from a test the creators has given us the
Nancy.Testing.Browser class, as we said the last time. This class helps
us to create our HTTP-requests and call them.
To create a Browser-object we need to supply a bootstrapper, that sets
up the environment that our test is executing in. It can be the default
DefaultNancyBootstrapper that uses all the common Nancy conventions or
settings.
Or ... it can be a lamda. Like this:
Pardon the funky formatting, but I think that requires
some explanation.
- On line 3 we're using a variable called "with". Hovering over that
parameter we see that it's of (hold on to your
hat...): ConfigurableBootstrapper.ConfigurableBoostrapperConfigurator.
It's a class that implements the
builder pattern, which makes it excellent to use
for building the executing context of our testing browser.
- On line 4 we are then using the Configurator to tell the browser to
just load the module called ConfigBootTestModule
It takes some getting used to but my suspicion is that users of Nancy
already use lambdas quite a lot and it doesn't scare them. The whole
routing feature is built around lambdas, for example.
For us it means that we get a very crisp syntax to express how we want
the environment to be set up.
The rest of this post dwells on some of the capabilities of the
Configurator and what we can use these capabilities for. I will not
cover everything, since that would be a very boring blog post and some
of the things you can swap out is beyond me why you would...
There's loads of code in here, but I reckon that's just what you wanted
### Module
The first and most obvious use for the ConfigurableBootstrapper is to
tell it to load the (Nancy)-Module your testing. Well actually if you
use the normal DefaultNancyBootstrapper it will load all the modules in
all referenced assemblies - that's the
Super Duper Happy Path in action right there :).
Here's an example of that
But it could be cumbersome, clumsy and slow to load every module in a
project and hence there's a possiblity to just load the one(s) your
planning to test. I use it as a best practice since it also narrows down
my test scope and asserts that things are where I thought they would
be.
Here's an examples of using the Module-methods:
Go to the repository to see other [ways to call
Module(s)](https://github.com/marcusoftnet/DiscoveringNancyThroughTests/blob/master/DiscoverNancy.Tests/DiscoverNancy.Tests/ConfigurableBootstrapper_Module.cs)
### Dependencies
The next thing that I think that most people would want to use is the
Dependecy and Dependecies methods. With these you can supply fake
implementations for any Dependency that a Module have. Yes - without
flexing any IoC container (other than
TinyIoC that's built into Nancy and that you almost
cannot see).
Let's see the Dependency-method in action:
As you can see it's merely a question of supplying the class as a type
parameter, but there's a number of ways to call the Dependency method.
Look here to see more of them in action.
Finally if your module have more than one dependency you can registrer
them all at once with the Dependencies-method. With this method you can
supply more than one Dependency.
This method also have a
lot of overloads, see here. In one of them me an
@grumpydev
found a bug here, where I plan to do my first contribution to the source
of Nancy, I've annotated the source to show the workaround.
### AutoRegistration
When all of that is out of the way you start to think that this is not
very Super Duper Happy, letting me configuring my dependencies like
that. Well there's help to be had through our friend:
EnableAutoRegistration.
This little method simply allows for the
convention based configuration that is found in
TinyIoC, meaning that if your interface is called IFoo it will
registrer the implementation Foo for example. As far as I understand the
current assembly is scanned first and then the referenced ones. So if
you have some mock instances of the dependent interfaces in the testing
assembly that will get registered first.
This all results in this test. Note that I don't have to hand
Nancy.Testing my dependencies. It just works.
However - this is a bit dangerous, of course. What happen if you have
more than one implementation for your interfaces in the test assembly?
What if the production implementation get's registered first - can the
order be trusted..? But for simple cases it's really nice.
### Other configurable properties
As I mentioned there's loads of things that you can swap out during
testing (or for your own implementation). All of it in fact. That's a
core property of Nancy itself, the whole framework. That in turn make
the testing experience excellent and apparently not that hard to write
for the NancyFx creators. They just used their own
frameworks extensibility.
So I wont write examples for all of them, but I thought I'd list them
and mention what you could use that for (in the cases I could see that):
- Binder - this gives you the ability to swap out the Nancy model
binder. If you are using one of your own or for some reason need to
mock the Nancy model binder of the way, this is the method for
you.
There are 2 versions: Binder and Binder(IBinder) for you to chose
from
- ContextFactory - this gives you the ability to mock out how the
Nancy Context-object is created. The Nancy Context the object that
Nancy creates for each incoming HTTP request. Yes, it's in the guts.
But you can still mock that stuff out and replace it with your own
factory for Nancy contextes.
There are 2 versions here too: ContextFactory
and ContextFactory(INancyContextFactory)
- StatusCodeHandlers - now we're venturing into countries that you can
questions if you ever need to do but there's an option to swap out
how a HTTP status code is handled.
There are two ways to call this method: StatusCodeHandler() and
StatusCodeHandlers(params Type\[\])
- RouteResolver - gives you the ability to swap out how routes are
resolved. Two versions: RouteResolver(IRouteResolver and
RouteResolver
- NancyModuleBuilder - this way you can swap the way that NancyModules
are built, from a a sent-in context object. Two versions exists:
NancyEngine(INancyEngine) and NancyEngine()
- NancyEngine - yes people. You can swap out the entire NancyEngine
itself. My guess is that many of the parts of that you can swap out
stems from the fact that the NancyEngine has dependencies on them.
But hey -
it can be done. Two version
exists: NancyEngine(INancyEngine) and NancyEngine()
The rest of them, and some of the ones that I've mentioned are thing
that I cannot see being used often, but still. It can be done. Most of
them works the same way as the Module and Depencendy-methods I've
described above
### Hooking into the pipeline
There are two more methods that can be useful: ApplicationStartup and
RequestStartup. With these you can hook into the pipeline of a HTTP
request and add functionality to it as needed.
Here's one example using the ApplicationStartup-method:
As you can see it's as easy as registering a block of code that is run
before each request. There are other events that you can hook into as
well (OnError and AfterRequest) but that's not as common, and works
exactly the same.
There's also a RequestStartup that works much the same way, but for a
single Request:
### Summary
As you can see there's loads of stuff that can be configured and swapped
out during testing of your Nancy application. In fact - it can be
replaced even when running the application in production. That's one of
the cornerstones of the design of Nancy - extensibility You can replace
almost anything with your own implementations.
That just happens to play right in the hands of a tester - creating a
awesome testing experience.
But there's still more that can be configured and in the next post I'll
dive (maybe not as deep) into the configurability of the Request object.
And the assertions on the Response-object.