简体   繁体   中英

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 ?*/);
    });
  });
}

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

Let's say we want to test [TargetClass.someMethodCallOtherStaticMethod]

Class StaticMethodClass {
  static int someStaticMethod() {};
}

Class TargetClass {
  int someMethodCallOtherStaticMethod() {
    return StaticMethodClass.someStaticMethod();
  }
}

We should refactor [[TargetClass.someMethodCallOtherStaticMethod]] for testing, like this:

Class TargetClass {
  int someMethodCallOtherStaticMethod({@visibleForTesting dynamic staticMethodClassForTesting}) {
    if (staticMethodClassForTesting != null) {
      return staticMethodClassForTesting.someStaticMethod();
    } else {
      return StaticMethodClass.someStaticMethod();
    }        
  }
}

Now you can write your test case like this:

// MockClass need to implement nothing, just extends Mock
MockClass extends Mock {}
test('someMethodCallOtherStaticMethod', () {
  // We MUST define `mocked` as a dynamic type, so that no errors will be reported during compilation
  dynamic mocked = MockClass();
  TargetClass target = TargetClass();
  when(mocked.someStaticMethod()).thenAnswer((realInvocation) => 42);
  expect(target.someMethodCallOtherStaticMethod(staticMethodClassForTesting: mocked), 42); 
})

 

The question was some while ago, but here is another solution. You can refactor calls to that static function to be called from a class "wrapper" method. This is a pattern I often use to mock requests to third party services.

Let me give you an example. To make it simple lets say Engine has 3 static methods that need to be mocked: brake() and accelerate() and speed().

class Car {
    int currentSpeed;

    void accelerateTo(int speed) {
         while(currentSpeed > speed) {
              Engine.brake();
              currentSpeed = Engine.speed();
         }
         while(currentSpeed < speed) {
              Engine.accelerate();
              currentSpeed = Engine.speed();
         }
    }
}

Now you want to mock all calls to the engine, to do so we could refactor the code to:

class Car {
    int currentSpeed;

    void accelerateTo(int speed) {
         while(currentSpeed > speed) {
              brake();
              currentSpeed = speed();
         }
         while(currentSpeed < speed) {
              accelerate();
              currentSpeed = speed();
         }
    }

    /// wrapper to mock Engine calls during test
    void brake() {
        Engine.brake();
    }

    /// wrapper to mock Engine calls during test
    int speed() {
        Engine.speed();
    }

    /// wrapper to mock Engine calls during test
    void accelerate() {
        Engine.accelerate();
    }

}

In the integration test you can now mock the 3 methods that interact with the static methods directly but you can now test your main method. While you could here also refactor the Engine class itself, often that class would be within a third party service.

This example is not based on the Volkswagen scandal ;).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM