2

Futures in Dart are the bane of my existence.

I have a class, which calls an async (Future) function to start up the database instance like so:

class DataManager {
  bool DbIsReady = false;
  Db _db;

  DataManager() {
    init_mongo_db();
  }

  void init_mongo_db() {
    print("Initializing MongoDB");
    _db = new Db("mongodb://127.0.0.1/test");
    _db.open().then((_) {
      DbIsReady = true;
    });
  }  

  Future<List> attemptLogin(String username, String password) {
    users = _db.collection("users");

    return // ... rest of code cut out for clarity
  }
}

This works OK server-side because when the server first starts up, the database is initialized. By the time a user actually attempts to log in, the database has been initialized and all is well. However, this fails miserably when I try to create an integration test for this. When I create the class, the database isn't yet initialized, so running the AttemptLogin routine fails.

DataManager db = new DataManager();
// This fails miserably, because db hasn't initialized yet.
db.AttemptLogin("test", "test"); 

Worse, the code uses the Dart DI framework, so I don't actually have direct control over the initialization of the DataManager class. This is the actual setup of the class:

setUp(() {
  app.addModule(new Module()
            ..bind(DataManager)
  app.setUp();
});

And then this is the call to test the login functionality, which ultimately calls the attemptLogin function which fails:

var req = new MockRequest("/user/login", contentType: 'JSON', method:'POST',
    body:JSON.encode(
      {"username" : 'test',
       "password" : 'test' }));

How does one deal with the async nature of the the database init and still do mock testing? Specifically, is there a way to force the attemptLogin() Future to somehow wait for the completion of the DataManager class initialization?

Thanks for your help, Greg

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Greg Sherman
  • 1,084
  • 1
  • 11
  • 22

2 Answers2

3

If your class has asynchronous setup, you can't use a normal constructor. In that case, I'd just use a factory method returning a future:

class DataManager {
  final Db _db;
  DataManager._(this._db);
  static Future<DataManager> createNew() {
    var db = new Db("mongodb://127.0.0.1/test");
    return db.open().then((_) => new DataManager._(db));
  }
  ...
};

I don't know how you hook that into your dependency injection framework, but I guess it can do something with static methods.

lrn
  • 64,680
  • 7
  • 105
  • 121
  • You can use the future wait to make async a sync. However, it is a better idea to just return a future in the long run. – ptDave Jul 06 '14 at 16:39
  • Future.wait still returns a Future, it can just wait for multiple futures at once, and return a single future with a list of results. There is no way to make an async result into a synchronous result, because a synchronous result needs to be available when the function returns, and an asynchronous result isn't there yet. – lrn Jul 07 '14 at 07:44
  • I agree with lrn's understanding of this - future.wait doesn't pause program execution. – Greg Sherman Jul 07 '14 at 19:05
  • @lrn: I tried your answer in my program and was not successful. The DI framework (which turns out to be the standard dart DI framework: https://pub.dartlang.org/packages/di) tried to inject a _Future object and failed. The error message was "Failed to execute server_lib.IsUserLoggedIn - type '_Future' is not a subtype of type 'DataManager' of 'db'." – Greg Sherman Jul 07 '14 at 19:08
  • It does look like the di framework doesn't handle asynchronously created values. Maybe put in a request for that from the authors. It is apparently only a prototype yet (according to https://github.com/angular/di.dart). – lrn Jul 07 '14 at 22:33
  • @lrn: I've given up on this approach. I'm now trying to initialize the DB as a first step in attemptLogin (and all other function calls). However, I've run into several issues on that front as well. I posted a StackOverflow question about this here: http://stackoverflow.com/questions/24678606/how-do-i-create-a-blank-future-in-dart-how-do-i-return-a-future-currently-in-p – Greg Sherman Jul 10 '14 at 14:06
1

what about using @lrn s solution like

setUp(() {
  return DataManager.createNew().then((dm) {
    app.addModule(new Module()
            ..bind(DataManager, toValue: dm);
    app.setUp();
  }
});

This way you already pass an initialized DataManager instance into the DI. If you request it later on you always can be sure it is already initialized.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • This looks reasonable - I'm going to try this later tonight. Not 100% sure that the setUp() function allows for Futures either, but it's worth a shot. – Greg Sherman Jul 10 '14 at 17:54
  • The unittest methods have specific support for futures. If you return a future the next step is only executed when the future returns. In this case, no tests are started before `createNew.then(()...) returns` This is also true for `test('xxx', () { return someAsync(); }` where the testrunner waits for the future to return before the test result is evaluated. Hint: easy to forget, if you have nested async methods each should have a return in front of the call to have a entirely connected chain. – Günter Zöchbauer Jul 10 '14 at 18:01
  • The code worked perfectly! Learn something new every day. :) Thanks, Günter! This seems to be the most sane approach to the issue, so I'll proceed in this direction. – Greg Sherman Jul 10 '14 at 23:21