Outside in Testing of ASP.NET
MVC Applications

Actually that's a lie, but whatever – let's
just do this.
Who am I?
●

I am @robashton

●

I write code (C#,JS,Clojure)

●

I do .NET for pizza + money + love

●

I do JS for just money + love

●

I do Clojure for love.

@robashton
Who are you?
●

You use ASP.NET MVC

●

You want to test stuff

●

●

You're probably working at enterprise
company ACME1337 Ltd
You're after some “real world”
comparison

@robashton
ASSUMPTIONS
●

You know what a container is

●

You know how ASP.NET MVC works
–

●

Controllers, action filters, model binding,
etc

If I gloss over a definition, either SHOUT
AT ME or ignore it as it's probably not
important
@robashton
Let me tell you a story about
every client ever...

@robashton
“We tried to do TDD because
Uncle Bob said so”

@robashton
“We should test trivial code*”

*If you're NASA
@robashton
“We should inject all your
dependencies”

1

@robashton
We should avoid ALL THE
COUPLING*

*Can recommend avoiding coupling ­­>

@robashton
How did that go for you?

@robashton
“We had 100% test
coverage”

@robashton
“Too many tests broke on
changing things”
:(

@robashton
“We spent more time fixing
tests than writing new code”

@robashton
“Our test suite slowed down
our build too much”

@robashton
“We deleted the tests
because they got in our way”

@robashton
No really – every client ever.

@robashton
So, let me say some things...

@robashton
TDD has failed us

@robashton
TDD is not “wrong”, it's just
not always appropriate

@robashton
TDD attempted by a lone
warrior is the Worst Thing
Ever.

@robashton
But that's all okay

@robashton
So... this talk then?
●

This is not a talk about TDD

●

This is a talk about testing

●

That's it.

@robashton
Boom, explosion.

@robashton
You're starting a new project

@robashton
What is it?
●

It is the latest in the line of our
company's own line of unique blue-sky
PRM systems. (Pony Relationship
Management)

@robashton
Where to start?

@robashton
This test is the FIRST THING
you do

@robashton
It is a statement of intent.

@robashton
It's a forcing mechanism.

@robashton
Okay you can do it in C# if
you want

@robashton
Avoid cucumber and
associated foolery
●

●

●

Forced/harmful abstraction
Only useful if non-devs are writing
acceptance criteria
This is nearly never the case

@robashton
So... that test?
●

We need to run some HTTP server

●

We need some sort of browser

●

Anything else...?

@robashton
What are the most important
things about this test?
●

Speed

●

Feedback

@robashton
Right now...
●

●

XSP from the Mono project is good
enough (feel free to play with IIS
express)
PhantomJS is the greatest thing ever.
This might not be the case next month
@robashton
Don't use PhantomJS though
●

Coupling our tests to Phantom is the
worst decision we can make

●

Use WebDriver to drive Phantom

●

Selenium is dead, long live Selenium
@robashton
Testing ASP.NET with
Phantom
WebDriver
HTTP
XSP Web Server

PhantomJS

@robashton

HTTP

Tests
Means we can do this
Firefox
WebDriver
HTTP
XSP Web Server

Chrome

Phantpm

@robashton

HTT
P

Tests
Or this
Firefox
HTTP
XSP Web Server

Chrome

Phantpm

@robashton

WebDriver
HTT
P

HTT
Selenium Grid P

Tests
I write my tests in CoffeeScript

@robashton
I write my contexts in JS

@robashton
I run it all using
●

NodeJS

●

Mocha

●

Mocha-Cakes

●

Should

@robashton
Do it your way
●

It's not important

●

Use whatever

●

C# is fine, I just like JS so I use it

@robashton
A good C# stack
●

NUnit or any other test framework

●

PhantomJS or any WebDriver browser

●

Coypu or any WebDriver client

@robashton
Testing Architecture
System setup/teardown
WebDriver Client

Test code

Client Abstractions (bob)

@robashton
So let's see it in action...

@robashton
A good test
●

Doesn't use CSS selectors

●

Doesn't show implementation details

●

Doesn't have any logic in it

●

Is understandable for any casual reader

@robashton
Instead of

@robashton
Consider

@robashton
Instead of

@robashton
Consider

@robashton
Avoid duplication in your UI
tests
●

●

●

Don't use the same selector more than
once across your contexts
Consider using conventions across your
UI
Push everything into your testing model
@robashton
Avoid duplication in your UI
tests
●

Consider using Capybara from Ruby

●

Consider using Coypu from .NET

@robashton
When to drop to the next
level?
●

There are two reasons to drop down a
level

●

“Low level tests are faster”

●

“You need more feedback”

@robashton
When to drop to the next
level?
●

There are two reasons to drop down a
level

●

“Low level tests are faster”

●

“You need more fine-grained feedback”

@robashton
Inverting the test pyramid?

@robashton
“inverting the test pyramid is
not a goal in itself”

@robashton
Times have moved on
●

Focus on speed

●

Focus on reducing effort

●

Focus on increasing feedback

●

Ignore dogma at every level

●

Listen to the pain
@robashton
Avoid solutions for problems
you haven't got yet
●

●

●

Starting on the outside means you can
change all the details later
Don't make decisions about technology
until you have to
Focus on features and iteration time
@robashton
Example: Defer all the
decisions
●

Let's store some ponies

@robashton
We have a controller

@robashton
How do we store a pony?

@robashton
IHousePonies

@robashton
InMemoryStable

@robashton
Usage

@robashton
This means
●

●

●

We end up in an in-memory (fast)
system for use in tests
We can get on and develop now
We can wait until we know what we
need before choosing something

@robashton
SqlStable

@robashton
Not for the sake of it
●

●

If you're using RavenDB it's got an inmemory built in
If you're using Redis, it's fast enough

“What's it going to cost vs what we're
going to gain”
@robashton
Example: Avoid layers
●

●

We've chosen to use
RavenDB/NHibernate
We've abstracted this like we were told
to

@robashton
Thin controller – yay!

@robashton
And a thin service!

@robashton
And a thin repository!

@robashton
And a container to wire them
up

@robashton
And tests for each!
­ Controller_can_call_delete_on_the_service.cs
­ Controller_can_call_add_on_the_service.cs
­ Controller_can_call_get_on_the_service.cs
­ Controller_will_return_a_view_model_from_the_service.cs
­ Service_will_call_delete_on_the_data.cs
­ Service_will_call_add_on_the_data.cs
­ Service_will_call_get_on_the_data.cs
­ Service_will_return_a_pony.cs
­ Sevice_will_return_null_when_there_is_no_pony
­ Data_will_persist_a_pony.cs
­ Data_will_delete_a_pony.cs :(
­ etc

@robashton
Does that feel good?

@robashton
With

Saving_a_pony_will_result_in_a_pony_existing.cs
A_pony_can_be_displayed_to_our_lovely_user.cs
A_pony_can_sadly_be_deleted_oh_how_sad.cs

@robashton
What did we learn?
●

●

●

You don't need a container when you
just have one external dependency
You don't need to create interfaces and
classes all over the show
Let these things emerge if the
complexity dictates it

@robashton
Avoid writing tests for trivial
code
●

●

●

Controller actions should be trivial
Model binding should be largely
automatic
It'll make you feel good, but that's about
it
@robashton
That said...
●

Drop down to lower level tests for code
with a large number of variants

●

That's things like validation rules

●

That's things like domain logic

@robashton
Don't avoid the hard stuff
●

Filters often hide complexity

●

They need testing

●

They often aren't tested

●

They're often brittle!

@robashton
The hard stuff
●

Test-drive the code for the actual logic

●

Then bring it in to ASP.NET MVC

●

Don't mock ASP.NET MVC!!

@robashton
Example: The hard stuff

@robashton
The test

@robashton
Better to have started with
the test

@robashton
Created code to deal with it

@robashton
And used that in a filter

@robashton
That's (really) it
●

Follow your nose

●

Listen to the pain

●

Focus on the feedback loop

●

Ignore everything I've just said

@robashton
Useful things
●

PhantomJS

●

WebDriver

●

Capybara / Coypu

●

Common sense

●

Selenium (not so much)

●

NodeJS/Coffeescript/Mocha/Mocha-Cakes
@robashton

Focus on the outside, testing in ASP.NET MVC