簡體   English   中英

Dart - 如何模擬返回未來的方法

[英]Dart - how to mock a method that returns a future

我有一個 class,它定義了一個返回 Future 的方法。 Future 包含一個 class 的列表,它也返回一個 future。

    class User{
      Future<List<Album>> albums(){

      };
    }
    class Album{
      Future<List<Photos>> photos(){
      }
    };

在測試另一個 class 時模擬這些類中的方法的最佳方法是什么?

我正在嘗試測試的 class 看起來有點像

class Presenter {
   Presenter( User user){
       user.albums().then( _processAlbums);
   }
   _processAlbums(List<Album> albums) {
      albums.forEach( (album)=>album.photos.then( _processPhotos));
  }
  _processPhotos(List<Photo> photos) {
     ....stuff
  }
}

我試着寫這樣的單元測試

class MockUser extends Mock implements User{}
class MockAlbum extends Mock implements Album{}
class MockPhoto extends Mock implements Photo{}

class MockFutureList<T> extends Mock implements Future<T>{

  MockFutureList( List<T> items){
    when( callsTo( "then")).thenReturn( items);
  }
}

void main(){

  test("constuctor should request the albums from the user ",(){

    MockUser user = new MockUser();

    MockAlbum album = new MockAlbum();
    List<Album> listOfAlbums = [ album];

    MockPhoto photo = new MockPhoto();
    List<Album> listOfPhotos = [ album];        
    user.when( callsTo( "albums")).thenReturn(  new MockFutureList(listOfAlbums));    
    album.when( callsTo( "photos")).thenReturn( new MockFutureList( listOfPhotos));

    PicasaPhotoPresentor underTest = new PicasaPhotoPresentor( view, user);

    user.getLogs( callsTo( "albums")).verify( happenedOnce);
    album.getLogs( callsTo( "photos")).verify( happenedOnce);

  });
}

這使我能夠測試構造函數調用了 user.photos() 方法,但沒有測試調用了 album.photos() 方法。

我不確定 mocking 一個 Future 是個好主意——創建一個包含模擬列表的“真實”Future 不是更好嗎?

任何想法都會非常有幫助!

由於您只想驗證UserAlbum中的方法是否被調用,因此您無需模擬Future

驗證模擬在這里有點棘手,因為你在構造函數中鏈接了未來。 通過稍微了解事件循環在Dart中的工作原理,我建議您在創建演示者后使用future並調用expectAsync

expectAsync函數告訴單元測試庫等待它被調用以驗證您的測試。 否則測試將成功完成,而不會滿足您的期望。

有了這個,這就是你的測試應該是這樣的:

import 'package:unittest/unittest.dart';

class MockUser extends Mock implements User {}
class MockAlbum extends Mock implements Album {}

void main() {
  test("constuctor should request the albums from the user ", () {
    var user = new MockUser();
    var album = new MockAlbum();
    user.when(callsTo("albums")).thenReturn(new Future(() => [album]));

    var presenter = new PicasaPhotoPresentor(view, user);

    // Verify the mocks on the next event loop.
    new Future(expectAsync(() {
      album.getLogs(callsTo("photos")).verify(happendOnce);
    }));
  });
}

以下是我設法做到的方法

1)定義FutureCallbackMock

class FutureCallbackMock extends Mock implements Function {
  Future<void> call();
}

2)從模擬中獲取函數並進行設置

FutureCallback onPressed = FutureCallbackMock().call;
completer = Completer<void>();
future = completer.future;

when(onPressed()).thenAnswer((_) => future);

3)驗證如此

verify(onPressed()).called(1);

4)如果需要,完成未來:

completer.complete();

注意:在flutter測試中,我必須將測試包裝在tester.runAsync就像這樣

      testWidgets(
          'when tapped disables underlying button until future completes',
          (WidgetTester tester) async {
        await tester.runAsync(() async {
           // test here
        });
      });

我能夠使用Mocktail做到這一點。 這是本文的來源,並解釋了如何將其集成到您的應用程序中。 這是一個完整的小部件測試,取決於這個要點代碼

關鍵是你需要聲明一個 Mock class 有一個call方法。 然后,您可以模擬返回Future的頂級 function。 您可以使用whenverify方法。

//Gist code
import 'package:gist/main.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/material.dart';

class LaunchMock extends Mock {
  Future<bool> call(
    Uri url, {
    LaunchMode? mode,
    WebViewConfiguration? webViewConfiguration,
    String? webOnlyWindowName,
  });
}

void main() {
  testWidgets('Test Url Launch', (tester) async {
    //These allow default values
    registerFallbackValue(LaunchMode.platformDefault);
    registerFallbackValue(const WebViewConfiguration());

    //Create the mock
    final mock = LaunchMock();
    when(() => mock(
          flutterDevUri,
          mode: any(named: 'mode'),
          webViewConfiguration: any(named: 'webViewConfiguration'),
          webOnlyWindowName: any(named: 'webOnlyWindowName'),
        )).thenAnswer((_) async => true);

    final builder = compose()
      //Replace the launch function with a mock
      ..addSingletonService<LaunchUrl>(mock);

    await tester.pumpWidget(
      builder.toContainer()<MyApp>(),
    );

    //Tap the icon
    await tester.tap(
      find.byIcon(Icons.favorite),
    );

    await tester.pumpAndSettle();

    verify(() => mock(flutterDevUri)).called(1);
  });
}

暫無
暫無

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

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