Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dart - how to mock a method that returns a future

I have a class that defines a method that returns a Future. The Future contains a list of class that also return a future.

    class User{
      Future<List<Album>> albums(){

      };
    }
    class Album{
      Future<List<Photos>> photos(){
      }
    };

What is the best way to mock the method in these classes when testing another class?

The class I am trying to test looks a bit like

class Presenter {
   Presenter( User user){
       user.albums().then( _processAlbums);
   }
   _processAlbums(List<Album> albums) {
      albums.forEach( (album)=>album.photos.then( _processPhotos));
  }
  _processPhotos(List<Photo> photos) {
     ....stuff
  }
}

I tried writing a unit test like this

class MockUser extends Mock implements User{}
class MockAlbum extends Mock implements Album{}
class MockPhoto extends Mock implements Photo{}

class MockFutureList<T> extends Mock implements Future<T>{

  MockFutureList( List<T> items){
    when( callsTo( "then")).thenReturn( items);
  }
}

void main(){

  test("constuctor should request the albums from the user ",(){

    MockUser user = new MockUser();

    MockAlbum album = new MockAlbum();
    List<Album> listOfAlbums = [ album];

    MockPhoto photo = new MockPhoto();
    List<Album> listOfPhotos = [ album];        
    user.when( callsTo( "albums")).thenReturn(  new MockFutureList(listOfAlbums));    
    album.when( callsTo( "photos")).thenReturn( new MockFutureList( listOfPhotos));

    PicasaPhotoPresentor underTest = new PicasaPhotoPresentor( view, user);

    user.getLogs( callsTo( "albums")).verify( happenedOnce);
    album.getLogs( callsTo( "photos")).verify( happenedOnce);

  });
}

This allowed me to test that the constructor called the user.photos() method, but not that the album.photos() method was called.

I am not sure that mocking a Future is a good idea - Would it not be better to create a 'real' Future that contains a list of Mocks?

Any ideas would be very helpful!

like image 637
richard Avatar asked Feb 28 '14 01:02

richard


2 Answers

Since you're only interested in verifying that methods in User and Album are called, you won't need to mock the Future.

Verifying the mocks gets a bit tricky here, because you're chaining futures inside the constructor. With a little understanding of how the event loop works in Dart, I recommend using a future and calling expectAsync after you create your presenter.

The expectAsync function tells the unit test library to wait until it's called to verify your tests. Otherwise the test will complete successfully without running your expectations.

With this, here's what your test should would look like:

import 'package:unittest/unittest.dart';

class MockUser extends Mock implements User {}
class MockAlbum extends Mock implements Album {}

void main() {
  test("constuctor should request the albums from the user ", () {
    var user = new MockUser();
    var album = new MockAlbum();
    user.when(callsTo("albums")).thenReturn(new Future(() => [album]));

    var presenter = new PicasaPhotoPresentor(view, user);

    // Verify the mocks on the next event loop.
    new Future(expectAsync(() {
      album.getLogs(callsTo("photos")).verify(happendOnce);
    }));
  });
}
like image 170
Dan Schultz Avatar answered Nov 05 '22 22:11

Dan Schultz


Here is how I managed to do it

1) Define FutureCallbackMock

class FutureCallbackMock extends Mock implements Function {
  Future<void> call();
}

2) get function from a mock and set it up

FutureCallback onPressed = FutureCallbackMock().call;
completer = Completer<void>();
future = completer.future;

when(onPressed()).thenAnswer((_) => future);

3) Verify like so

verify(onPressed()).called(1);

4) Complete the future if needed:

completer.complete();

NOTE: in flutter tests I had to wrap my test in tester.runAsync like so

      testWidgets(
          'when tapped disables underlying button until future completes',
          (WidgetTester tester) async {
        await tester.runAsync(() async {
           // test here
        });
      });

like image 3
almeynman Avatar answered Nov 05 '22 23:11

almeynman