UPDATE: Stephen Toub pointed out that in .NET 4.5 you don’t need my
CreatePseudoTask() helper method. See the bottom of this post for
more information.
If you’ve been coding in VS11 Beta with .NET 4.5 you may have started experimenting with using async and await in your programs. You also probably noticed a lot more of the APIs you consume are starting to expose asynchonous methods using Task and Task<T>.
This technology let’s use specify that operations are long running and should be expected to not return quickly. You basically get to fire off async processes without you having to manage the threads yourself. Behind the scenes, the necessary state machine code is created and, as they say, “it just works”.
I would really recommend reading all the great posts by Stephen Toub and others over on the PFX Team blog. And of course the MSDN Docs on the Task Parallel Library should be reviewed too.
But did you know that in VS11 Beta you can now create async unit tests? Both MS-Test and the newest version of xUnit.net now support the idea of a unit test that is async, and can therefore use the await keyword to block on a call that returns a Task.
One of the interesting things about this occurs when you use async with a faked
interface that contains an async method. Consider the case where you have an
interface that returns Task<string> because it is expected that some or all
of the implementors could be long running. Your interface definition might look
like this:
1 2 3 4 | |
When you are testing a class that consumes this interface, you will want to provide a fake implementation of that method. So what is the best way to do that?
(Note: I will use VS11 Fakes for this example but it really doesn’t matter.)
You might write a test like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Assuming DoSomething() produces the formatted string that is expected, this
test will work. But there’s a bit that is unfortunate…
You actually did spin off a background thread when you called Task.Run(). You can confirm this with some well-placed breakpoints and looking at the threads.
But did you need to do that in your fake object? Not really. It probably slowed your test down by a bit and really isn’t required.
The System.Threading.Tasks namespace includes a class you can use to help you
with these kinds of things: TaskCompletionSource<T> (see MSDN Docs). This very cool
class can be used for a lot of different things like making event-based async
live in a TAP world, etc.
Stephen Toub says a lot about TCS in his post The Nature of TaskCompletionSource<TResult> but the part most relevant to us here is where he says:
Unlike Tasks created by Task.Factory.StartNew, the Task handed out by
TaskCompletionSource<TResult> does not have any scheduled delegate associated
with it. Rather, TaskCompletionSource<TResult> provides methods that allow you
as the developer to control the lifetime and completion of the associated Task.
This includes SetResult, SetException, and SetCanceled, as well as TrySet
variants of each of those.
It “does not have any schedule delegates associated with it.” That sounds perfect!
UPDATE: This code is only required in the .NET 4.0 version of Task Parallels. See below for an updated .NET 4.5 version of this test that doesn’t require my helper method.
So what I’m going to do is use TCS to create a task that just contains the concrete return value, acting as if the long running operation has happened, and returning a Task that the consuming code can treat normally.
Rewriting that last test using a TCS it would look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
Now I no longer have the background thread! But that chunk of code where I create the TCS is annoying so I can refactor it out into a reusable helper method:
1 2 3 4 5 6 7 8 9 | |
Now I can rewrite the test to this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Nice and simple, and easy to read, without all the mess of creating real scheduled background Task delegates.
What do you think? Useful? I’ve found it to help me a bit when writing tests against async stuff.
UPDATE: Using Task.FromResult in .NET 4.5
Apparently this patterns was common enough in the .NET 4.0 version of TPL that the team decided to just “make it so” and bake it in so we don’t need the helper method anymore. And since it is baked in, it is probably optimized to perform even better.
Here is the updated test using the new Task.FromResult method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Enjoy!