felangel / mocktail

A mock library for Dart inspired by mockito
https://pub.dev/packages/mocktail
MIT License
617 stars 81 forks source link

How to mock with await for ? #108

Closed woprandi closed 3 months ago

woprandi commented 2 years ago

I have this kind of code

void main() {
  runApp(Provider(
    create: (_) => Repo(),
    child: MyApp(),
  ));
}

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  WebSocket? _ws;
  var value = "";
  Timer? _timer;

  void _connect() async {
    try {
      print("connecting...");
      _ws = await context.read<Repo>().websocket.timeout(Duration(seconds: 10));
      print("connected");

        await for (var s in _ws!) {
        setState(() {
          value = s;
        });
      }
    } finally {
      print("disconnected");
      _timer = Timer(Duration(seconds: 10),  _connect);
    }
  }

  @override
  void initState() {
    _connect();
    super.initState();
  }

  @override
  void dispose() {
    _timer?.cancel();
    _ws?.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Text(value),
      ),
    );
  }
}

class Repo {
  Future<WebSocket> get websocket => WebSocket.connect('ws://...');
}

This code works. I would like to test the automatic reconnection when websocket connection is closed (with mocktail to mock). I discovered that stream.listen(...) is called under the hood when using await for syntax. But I'm unable to get out of the loop during test. This is what I tried.

import 'dart:async';
import 'dart:io';

import 'package:flutter_playground/main.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:provider/provider.dart';

class MockWS extends Mock implements WebSocket {}

class MockRepo extends Mock implements Repo {}

void main() {
  testWidgets('auto reconnect', (WidgetTester tester) async {
    final ws = MockWS();
    final repo = MockRepo();
    final ctrl = StreamController();
    when(() => repo.websocket).thenAnswer((_) async => ws);
    when(() => ws.listen(
          any(),
          onDone: any(named: 'onDone'),
          onError: any(named: 'onError'),
          cancelOnError: any(named: 'cancelOnError'),
        )).thenAnswer((invocation) {
      return ctrl.stream.listen(
        invocation.positionalArguments[0],
        onDone: invocation.namedArguments['onDone'],
        onError: invocation.namedArguments['onError'],
        cancelOnError: invocation.namedArguments['cancelOnError'],
      );
    });
    when(() => ws.close()).thenAnswer((invocation) async => null);
    await tester.pumpWidget(Provider<Repo>.value(
      value: repo,
      child: MyApp(),
    ));

    // Try to close the stream
    await ctrl.close();
    await tester.pump(Duration(seconds: 10));
    verify(() => repo.websocket).called(2);
  });
}

But the test fails, it does not get out the await for loop. How to emulate a loop exit ? I didn't get any answer on SA so maybe I'll get a fast answer here.

felangel commented 2 years ago

Hi @woprandi 👋 Thanks for opening an issue!

Are you able to provide a link to a minimal reproduction sample? It would be much easier to help if I'm able to run/debug the issue locally, thanks! 🙏

woprandi commented 2 years ago

@felangel well, what's the problem with the code I posted ?

felangel commented 2 years ago

It's not clear what versions of dependencies you're using are and what environment you're running in. It would be much easier to help if you could provide a link to a GitHub repo that I could clone and run to reproduce the issue.

I can try to put together an example based on the snippets you provided but it's always much easier and less time consuming to investigate when there is a minimal reproduction sample that can quickly be cloned/run.

woprandi commented 2 years ago

OK you can clone here https://github.com/woprandi/flutter_playground You will need a websocket server to simulate (re)connections. You can use the test.py file (pip install websockets also). As you can see, the code works as expected but not the test

woprandi commented 2 years ago

@felangel Were you able to take a look ?

felangel commented 5 months ago

@woprandi is this still an issue? This fell off my radar, apologies :(

woprandi commented 5 months ago

@felangel No problem I didn't remember about this issue. I don't know if it's still relevant. I'd need to take 5 min to retry

felangel commented 3 months ago

Closing for now since this issue is quite old and there isn't a minimal reproduction sample. If this is still a problem please file a new issue with a link to a reproduction sample, thanks!