2

好的,请耐心等待,因为我刚刚开始学习小部件测试。我有一个 MainAppBar 小部件,它在小部件树中的上方有一个 AuthProvider,如果用户实际上已通过身份验证,则需要执行身份验证检查和后续注销。这是一个愚蠢的场景,但能够测试它对于我的应用程序中的许多其他场景很有用。

IconButton(
 icon: Icon(Icons.logout), 
 onPressed: () {
   if (Provider.of<AuthProvider>(context, listen: false).isAuthenticated) {
     Provider.of<AuthProvider>(context, listen: false).signOut();
     Navigator.pushReplacementNamed(context, '/login');
   }
 }
)

我像这样嘲笑 AuthProvider

class MockAuthProvider extends Mock implements AuthProvider {}

使用 mockito 并像这样包装小部件

MockAuthProvider mockAuthProvider = MockAuthProvider();
  
Widget makeTestableWidget({Widget child}) => MaterialApp(
  home: ChangeNotifierProvider<AuthProvider>(
    create: (_) => mockAuthProvider,
    child: Scaffold(
      body: child,
    ),
  ),
);

bool是基于简单的isAuthenticated枚举状态检查,所以只要设置状态,就可以测试真假场景等。在我的测试中,我想设置一个状态,测试是否调用了signOutand 。Navigator

testWidgets('Clicking on logout icon...', (WidgetTester tester) async {
  mockAuthProvider.setAuthProviderStatus(AuthProviderStatus.Unauthenticated);
  print(mockAuthProvider.isAuthenticated);
  await tester.pumpWidget(makeTestableWidget(child: MainAppBar(title: 'test')));
    
  var icon = find.byIcon(Icons.logout);
  await tester.tap(icon);
  await tester.pump();

  verifyNever(mockAuthProvider.signOut());
});

setAuthProviderStatussetter 设置状态并进行isAuthenticated基本检查:

bool get isAuthenticated {
  if (status == AuthProviderStatus.Authenticated) {
    return true;
  }
  return false;
}

AuthProvider 将状态初始化为默认值Uninitialized,因此尽管可以将此检查扩展为考虑 null,但现在应该没问题。在我的 MainAppBar 小部件中,isAuthenticatedbool 以 null 的形式返回并抛出异常Failed assertion: boolean expression must not be null,因为 if 语句需要 bool,而 null 会导致它失败。我的测试中的打印print(mockAuthProvider.isAuthenticated);也是空的。我已经通过实际的 AuthProvider 成功进行了测试,并且没有遇到同样的问题,尽管这显然不适用于测试目的。

如何在模拟 Provider 中设置值,以便它们下面的小部件知道它们?

4

1 回答 1

2

万一有人偶然发现...要实现这一点,您可以根据需要对任何提供程序功能进行存根:

代替:

mockAuthProvider.setAuthProviderStatus(AuthProviderStatus.Unauthenticated);  

你可以像这样使用mockito:

when(mockAuthProvider.isAuthenticated)
  .thenReturn(<VALUE YOU WANT TO TEST>);

因此,当执行该行时Provider.of<AuthProvider>(context, listen: false).isAuthenticated, isAuthenticated 将具有一个存根值。

以下是我最终为 Icon 的 onPressed 功能编写的测试:

void setIsAuthenticated(bool isAuthenticated) {
  when(mockAuthProvider.isAuthenticated)
    .thenReturn(isAuthenticated);
  when(mockAuthProvider.hasListeners).thenReturn(false);
}

testWidgets('Clicking on logout icon does not call signOut if user is not authenticated', (WidgetTester tester) async {
  setIsAuthenticated(false);

  await tester.pumpWidget(makeTestableWidget(child: MainAppBar(title: 'Test')));

  var icon = find.byIcon(Icons.logout);
  await tester.tap(icon);
  await tester.pump();

  verifyNever(mockAuthProvider.signOut());
});

testWidgets('Clicking on logout icon calls signOut if user is '
  'authenticated AND push replace route', 
  (WidgetTester tester) async {
      
  setIsAuthenticated(true);

  await tester.pumpWidget(makeTestableWidget(child: MainAppBar(title: 'Test')));

  var icon = find.byIcon(Icons.logout);
  await tester.tap(icon);
  await tester.pumpAndSettle();

  verify(mockAuthProvider.signOut());
  verify(mockObserver.didPush(any, any));
  expect(find.text('Login'), findsOneWidget);
});
于 2020-12-02T21:30:38.390 回答