簡體   English   中英

如何使用 Mockito 在 Flutter 中模擬 static 方法?

[英]How to mock a static method in Flutter with Mockito?

我有一個文件 function fetchPosts()負責從服務器獲取新帖子並將它們存儲在本地 sqlite 數據庫中。

按照 sqflite doc的建議,我將單個 ref 存儲到我的數據庫中。

這是我的database.dart的內容。dart 文件:

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.
}

然后我在我的fetchPosts.dart文件中使用它

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]);
  }
}

在我的測試中,如何verify DBProvider.insert()是否已被調用?

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

最終,我不得不重寫我的database.dart以使其可測試/可模擬。
這是新文件:

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);
  }

  // ...
}

現在我可以使用與 http.Client 相同的技巧。 謝謝@RémiRousselet

假設我們要測試 [TargetClass.someMethodCallOtherStaticMethod]

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

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

我們應該重構 [[TargetClass.someMethodCallOtherStaticMethod]] 進行測試,如下所示:

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

現在您可以像這樣編寫測試用例:

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

 

這個問題是不久前的,但這是另一個解決方案。 您可以重構對 static function 的調用,以便從 class “包裝器”方法調用。 這是我經常用來模擬對第三方服務的請求的模式。

讓我給你舉個例子。 為簡單起見,假設 Engine 有 3 個 static 方法需要被模擬:brake()、accelerate() 和 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();
         }
    }
}

現在您想模擬對引擎的所有調用,為此我們可以將代碼重構為:

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();
    }

}

在集成測試中,您現在可以模擬與 static 方法直接交互的 3 個方法,但您現在可以測試您的主要方法。 雖然您也可以在這里重構引擎 class 本身,但通常 class 將在第三方服務中。

這個例子不是基於大眾汽車丑聞;)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM