Testing function containing async function in Dart
Asked Answered
D

2

6

I want to test a function which invokes other async function and I don't know how to write it. Function would go like this:

function(X x, Y y) {
    x.doSomethingAsync().then((result) {
        if (result != null) {
            y.doSomething();
        }
    }
}

I would like to mock both X and Y, run X and than verify that y.doSomething() gets invoked. However I do not know how to wait for x.doSomethingAsync() to complete. I was thinking about doing some waiting before assertion but it doesn't seem like reliable solution.
Any help please? :)

Deeann answered 4/3, 2018 at 6:40 Comment(0)
M
7

You can use async/await in dart. Which would simplify quite a lot your function :

function(DoSomething x,  DoSomething y) async {
  final result = await x.doSomethingAsync();
  if (result != null) {
    y.doSomething();
  }
}

This way, the function will not complete until x.doSomething has completed. You can then test your function using the same async/await operators with an async test.

You'd have this :

test('test my function', () async {
  await function(x, y);
});

Okey, but how do I test if the functions got called ?

For this, you can use mockito which is a mock package for tests purposes.

Let's assume your x/y class is :

class DoSomething {
  Future<Object> doSomethingAsync() async {}
  void doSomething() {}
}

you could then use Mockito by mocking your class methods using :

// Mock class
class MockDoSomething extends Mock implements DoSomething {
}

finally you could use that mock inside your test by doing :

test('test my function', () async {
  final x = new MockDoSomething();
  final y = new MockDoSomething();
  // test return != null
  when(x.doSomethingAsync()).thenReturn(42);
  await function(x, y);

  verifyNever(x.doSomething());
  verify(x.doSomethingAsync()).called(1);
  // y.doSomething must not be called since x.doSomethingAsync returns 42
  verify(y.doSomething()).called(1);
  verifyNever(y.doSomethingAsync());

  // reset mock
  clearInteractions(x);
  clearInteractions(y);

  // test return == null
  when(x.doSomethingAsync()).thenReturn(null);
  await function(x, y);

  verifyNever(x.doSomething());
  verify(x.doSomethingAsync()).called(1);
  // y must not be called this x.doSomethingAsync returns null here
  verifyZeroInteractions(y);
});
Montsaintmichel answered 4/3, 2018 at 11:27 Comment(7)
Hey, thank you for answer, but my function is not and cannot be async.Katheryn
Considering your function use then, your function is async alreadyGraphology
Oh, I see, I didn't know I can call await on function even if I didn't declare it as async. Thank you for your answer then :)Katheryn
It's not about declaring a function async or not. async functions only enforce the function to return Future<whatever>. While await operator isn't used on function, but on Future. You can await a variable if you wanted to, as long as it's a Future.Graphology
Ok, so what can I do if I have nested not-async functions where at the and is then clause. How can I test top level function?Katheryn
Well, I'm using an external package so I can't do it. But thank you, you helped me :)Katheryn
This question has fundamentally NOT been answered. The fact that a function uses "then" doesn't make it async, in terms of being able to call await on it. You can call a function which is NOT async, have it execute an async function (as in the original question), prescribe how to handle the result with "then", and have the function return, so that it doesn't wait for anything to complete. This is a valid case, and we still don't know how to unit test it.Utham
U
0

The accepted answer doesn't actually answer the original question. And some of the comments are misleading.

The real answer to the original question is to use

await untilCalled()

To test a function which calls an async function and prescribes the result with then, inside the test you can do:

function(mockX, mockY);
await untilCalled(mockY.doSomething());
verify(mockY.doSomething).called(1)

This will cause the test to wait for the async operation to complete and call the doSomething() function, and then you can verify the results.

Be careful, though, because if the function you're waiting for is never called, the test will wait forever. It's wise when using untilCalled to also set a timeout:

await untilCalled(untilCalled(mockY.doSomething())).timeout(Duration(seconds: 1));

This way, if the function you're waiting for doesn't get called, the test will continue, and then fail.

Utham answered 12/1 at 15:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.