【问题标题】:How to mock a static method in Flutter with Mockito?如何使用 Mockito 在 Flutter 中模拟静态方法?
【发布时间】:2020-02-02 21:53:46
【问题描述】:

我有一个文件fetchPosts() 负责从服务器获取新帖子并将它们存储在本地sqlite 数据库中。

按照 sqflite doc 的建议,我将单个 ref 存储到我的数据库中。

这是我的database.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 ?*/);
    });
  });
}

【问题讨论】:

  • 你不能模拟静态成员。如果您需要模拟,请不要将它们设为静态
  • @RémiRousselet,感谢您的洞察力。我很好奇,不允许静态成员被嘲笑的原因是什么?
  • 好问题。我怀疑这是不合常规的,没有任何理由。面向对象的开发者喜欢惩罚 fp 开发者的选择。

标签: unit-testing flutter sqflite


【解决方案1】:

最终,我不得不重写我的 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

【讨论】:

    【解决方案2】:

    这个问题是不久前的问题,但这是另一种解决方案。您可以重构对该静态函数的调用,以便从类“包装器”方法中调用。这是我经常用来模拟对第三方服务的请求的一种模式。

    让我举个例子。为了简单起见,假设 Engine 有 3 个需要模拟的静态方法: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();
        }
    
    }
    

    在集成测试中,您现在可以模拟与静态方法直接交互的 3 个方法,但您现在可以测试您的主要方法。虽然您也可以在此处重构 Engine 类本身,但该类通常位于第三方服务中。

    这个例子不是基于Volkswagen scandal;)。

    【讨论】:

      【解决方案3】:

      假设我们要测试 [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); 
      })
      
       
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-08-30
        • 2014-02-02
        • 1970-01-01
        相关资源
        最近更新 更多