unit-testingfluttersqflite

How to mock a static method in Flutter with Mockito?


I have a file a function fetchPosts() which is in charge of getting new Posts from a server and store them in a local sqlite database.

As recommended on the sqflite doc, I store a single ref to my database.

Here is the content of my database.dart file:

import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

class DBProvider {
  DBProvider._();
  static final DBProvider db = DBProvider._();

  static Database _database;

  static Future<Database> get database async {
    if (_database != null) return _database;
    // if _database is null, we instantiate it
    _database = await _initDB();
    return _database;
  }

  static Future<Database> _initDB() async {
    final dbPath = await getDatabasesPath();
    String path = join(dbPath, 'demo.db');

    return await openDatabase(path, version: 1, onCreate: _onCreate);
  }

  static Future<String> insert(String table, Map<String, dynamic> values) async { /* insert the record*/ }

  // Other functions like update, delete etc.
}

Then I use it as such in my fetchPosts.dart file

import 'dart:convert';
import 'package:http/http.dart' as http;
import '../services/database.dart';

const url = 'https://myapp.herokuapp.com';

Future<void> fetchPosts() {
  final client = http.Client();
  return fetchPostsUsingClient(client);
}

Future<void> fetchPostsUsingClient(http.Client client) async {
  final res = await client.get(url);
  final posts await Post.fromJson(json.decode(response.body));

  for (var i = 0; i < posts.length; i++) {
    await DBProvider.insert('posts', posts[i]);
  }
}

In my test, how can I verify that DBProvider.insert() has been called?

fetchPosts_test.dart

import 'package:test/test.dart';
import 'package:http/http.dart' as http;
import 'package:mockito/mockito.dart';
import 'package:../services/fetchPosts.dart';

// Create a MockClient using the Mock class provided by the Mockito package.
// Create new instances of this class in each test.
class MockClient extends Mock implements http.Client {}

void main() {
  group('fetchPosts', () {
    test('update local db', () async {
      final client = MockClient();

      // Use Mockito to return a successful response when it calls the provided http.Client.
      when(client.get()).thenAnswer((_) async => http.Response('{"title": "Test"}', 200));

      await fetchPostsWithClient(client);

      verify(/* DBProvider.insert has been called ?*/);
    });
  });
}

Solution

  • Eventually, I had to rewrite my database.dart to make it testable / mockable.
    Here's the new file:

    import 'dart:async';
    import 'package:path/path.dart';
    import 'package:sqflite/sqflite.dart';
    
    class DBProvider {
      static final DBProvider _singleton = DBProvider._internal();
    
      factory DBProvider() {
        return _singleton;
      }
    
      DBProvider._internal();
    
      static Database _db;
    
      static Future<Database> _getDatabase() async {
        if (_db != null) return _db;
        // if _database is null, we instantiate it
        _db = await _initDB();
        return _db;
      }
    
      static Future<Database> _initDB() async {
        final dbPath = await getDatabasesPath();
        String path = join(dbPath, 'demo.db');
    
        return openDatabase(path, version: 1, onCreate: _onCreate);
      }
    
      Future<String> insert(String table, Map<String, dynamic> values) async {
        final db = await _getDatabase();
        return db.insert(table, values);
      }
    
      // ...
    }
    

    Now I can use the same trick as with the http.Client. Thank you @RĂ©miRousselet