Hi folks. Sorry for the delay in this, but I've just pushed the
changes to my fork in github (
http://github.com/citizenmatt/SpecFlow).
It's on the Async2 branch, and is built on top of the 1.6.1 release
(meaning it doesn't include any of the code that's been merged into
master since the 1.6.1 release). I'll get a pull request together
shortly.
There's nothing special you need to do to build, and there's a
Runtime.Silverlight.Async silverlight test project with a couple of
scenarios to exercise the async stuff. It updates both the generator
and the runtime, but there should be no breaking changes with existing
feature files. The simplest thing is to update all of the dlls in your
Program Files\TechTalk\SpecFlow folder, and reference
TechTalk.SpecFlow.Silverlight3.dll in your silverlight project.
Using the async stuff is very straightforward, but it's probably best
to give a little background first.
The async support is NOT parallel support. Your steps will still
execute serially, one after another. The async support here allows
your test to give up control flow, and allow other operations to
execute before continuing.
The support is also runtime specific. For Silverlight, the test
framework will run your tests on the UI thread, which can cause
deadlocks if you want to use web requests (requests and responses are
bizarrely marshalled onto the UI thread). Async support allows your
test to give control back to the runtime to let other operations to
continue before you get called back again.
For the most part, this is handled transparently. Each SpecFlow step
is run in its own async context - as a separate work item. After each
step, control is given back to the system, and other async operations
have the opportunity to continue or complete.
When you want to wait for an async operation to be complete, or to
sleep for a while, call the Enqueue methods on AsyncContext. You can
get an instance of AsyncContext via AsyncContext.Current (I tend to
wrap these in an AsynchronousSteps base class that derives from Steps.
Might add that, actually)
* EnqueueDelay will perform a non-blocking sleep. e.g.
context.EnqueueDelay(TimeSpan.FromSeconds(2))
* EnqueueConditional will perform a non-blocking wait until a
condition is true. e.g. context.EnqueueConditional(() => flag == true)
* EnqueueCallback will schedule a simple action to be performed after
the currently enqueued actions are done. e.g.
context.EnqueueCallback(() => doSomething());
When you enqueue a work item, it gets added to the queue to be
executed after the current step, BUT YOUR STEP WILL CONTINUE
EXECUTING!
E.g.
public void When_I_do_something()
{
// e.g. a web request
StartSomethingAsync();
// Enqueue a non-blocking wait for the async op to complete
EnqueueConditional(() => myAsyncSomethingHasCompleted == true);
// WARNING! The operation hasn't completed yet!
// UseResultOfAsyncOperation();
// Enqueue an action for the result. This will only get called
once
// myAsyncSomethingHasCompleted is true
EnqueueCallback(() => UseResultOfAsyncOperation());
}
Similarly, if you call a step from within a step definition, then it
isn't called immediately, it is enqueued to be run at the end of the
currently executing step (and in fact, after any steps that have
already been enqueued).
To summarise:
1. Control flow is relinquished after each step
2. You can sleep using EnqueueDelay
3. You can perform a non-blocking wait until something is true using
EnqueueConditional
4. You can enqueue further work items using EnqueueCallback
5. Given/When/Then are enqueued as new async work items
6. Any code written after a call to EnqueueXXX or Given/When/Then will
execute BEFORE the enqueued work items
Let me know how you get on. It's not as complex as it might seem,
honest.
Thanks
Matt