Home > Blockchain >  How does testing for synchronous changes in an async function work in Dart's test package?
How does testing for synchronous changes in an async function work in Dart's test package?

Time:07-29

I'm following Reso Coder's video about unit tests in Flutter / Dart. In the video, he is testing a class that makes an async call to a remote service (mocked using Mockito):

Class being tested (not the full code):

Class ServiceChangeNotifier extends ChangeNotifier {
 final Service _service;

 ServiceChangeNotifier(Service this._service);

 bool _isLoading = false;
 bool get isLoading => _isLoading;

 List<int> _res = [];
 List<int> get res => _res;

 Future<void> doService() async {
   _isLoading = true;
   notifyListeners();
   _res = await _service.runService();
   _isLoading = false;
   notifyListeners();
 }
 

Notice he's changing _isLoading to true before the service call, and then to false after.

Now, in the test, he wants to verify that the loading state gets updated correctly to true before the service call, and then to false after the service. To do so, he is setting the call to the async service method into a Future variable, then asserts the true change, then awaits on the future variable, and then asserts for false:

Unit test code

 final ServiceChangeNotifier sut = ServiceChangeNotifier(serviceMockup);
 
 test('....',
  () async {
    // test setup...

    final future = sut.doService() // 1
    expect(sut.isLoading, true); // 2
    await future; // 3
    expect(sut.isLoading, false); // 4

    // ...
  }
   

I understand why you can await the method before testing for true, because it will change to false before the expect(true) gets to run. What I don't understand is how the check for true (line 2 above) works. Is the logic of line 1 (future assignment) the one that triggers the execution of the doService method, or is it the await in line 3?

I suspect I'm missing something basic about how Future/await works and/or how expect() works in the test library. This question is somewhat related to this question, but here I'm not asking about the async part of the doSerivce method, but rather how the synchronous assignments to _isLoading is being evaluated in light of the asynchronous call.

CodePudding user response:

When you execute an asynchronous function, as much of it as possible is executed synchronously; that is, its function body is executed until it reaches an await (which is syntactic sugar for returning a Future and registering appropriate completion callbacks).

So given:

final future = sut.doService() // 1
expect(sut.isLoading, true); // 2
await future; // 3
expect(sut.isLoading, false); // 3
  1. sut.doService() is invoked immediately. doService's body immediately sets _isLoading = true, invokes notfifyListeners() [sic], invokes _service.runService(), and then returns a Future.
  2. sut.isLoading at this point is true.
  3. The Future returned by step 1 is awaited, so execution returns to the event loop. The Future from _service.runService() eventually completes, which executes the rest of doService()'s body, setting _isLoading = false and invoking notfifyListeners() again.
  4. sut.isLoading at this point is false.
  • Related