Here is an answer to a question that keeps popping up every once in a while: how do you stub out asynchronous actions in your class under test? Assuming that you can’twon’t change the code and refactor it (usual case in most legacy systems), here is a class with an async action you’d like to stub:
public class ClassWithAsync
{
public void Do(int input,Action<int> resultCallback)
{
ThreadPool.QueueUserWorkItem(state =>
{
Thread.Sleep(1000*3);
resultCallback(input*2);
});
}
}
The ThreadPool.QueueUserWorkItem will invoke our callback asynchronously. Here are two versions of the same test that you can write. The first one is the strogly typed version, which will required the pro or enterprise versions of Typemock Isolator. The second one will work with the free community edition of Typemock.
10 [Test]
11 public void AsyncMocking_NaturalMocks()
12 {
13 bool wasCalled = false;
14 int passedResult = 0;
15
16 ClassWithAsync target = RecorderManager.CreateMockedObject<ClassWithAsync>();
17
18 using (RecordExpectations rec = new RecordExpectations())
19 {
20 target.Do(0,null);
21 rec.Return(new DynamicReturnValue(delegate(object[] parameters, object context)
22 {
23 Action<int> theUserCallback =
24 parameters[1] as Action<int>;
25 theUserCallback(11);
26 return null;
27
28 }));
29 }
30
31 target.Do(5, delegate(int result)
32 {
33 passedResult = result;
34 wasCalled = true;
35 });
36 Assert.IsTrue(wasCalled);
37 Assert.AreEqual(11, passedResult);
38
39 }
The way this test works:
in line 16: we create a mocked version of the class we’d like to stub out. then we start a recording session with Isolator where all method calls are recorded. in line 20 we invoke the method for recording purposes. essentially saying “a call to target.do()” will be made later. any parameters we pass in that line are ignored.
rec.Return tells the recorder what to return or execute when the method is actually called. in this case we are create something called a DynamicReturnValue which allows us to send in our own delegate that does whatever we want. in this case we are taking in the parameters that were sent to this method (the second one is the user’s delegate).
when the recording block has ended (the end of the using block) we simply invoke the class. this is code that will usually not be in a test, but will be in the production code that we invoke. we are just simulating what the production code might do. we pass in our own delegate as the callback to the method, and in it we just set some variables that we will assert against. (We pass in 11 to show that we control the output. the original class would have returned 10.
Here is the community edition version of this test: no recordreplay support but should work just the same:
13 [Test]
14 public void AsyncMocking_ReflectiveMocks()
15 {
16 bool wasCalled = false;
17 int passedResult = 0;
18
19 MockObject<ClassWithAsync> mockObject = MockManager.MockObject<ClassWithAsync>();
20 mockObject.ExpectAndReturn(“Do”,
new DynamicReturnValue(delegate(object[] parameters, object context)
21 {
22 Action<int> theUserCallback =
23 parameters[1] as Action<int>;
24 theUserCallback(((int)parameters[0]) *2 +1);
25 return null;
26
27 }));
28
29 ClassWithAsync mockedClass = mockObject.MockedInstance;
30 mockedClass.Do(5, delegate(int result)
31 {
32 passedResult = result;
33 wasCalled = true;
34 });
35 Assert.IsTrue(wasCalled);
36 Assert.AreEqual(11,passedResult);
37
38 }
Any questions?