mocktail icon indicating copy to clipboard operation
mocktail copied to clipboard

How to mock with await for ?

Open woprandi opened this issue 2 years ago • 7 comments

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.

woprandi avatar Jan 27 '22 10:01 woprandi

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! 🙏

felangel avatar Mar 01 '22 19:03 felangel

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

woprandi avatar Mar 01 '22 20:03 woprandi

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.

felangel avatar Mar 01 '22 20:03 felangel

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 avatar Mar 02 '22 09:03 woprandi

@felangel Were you able to take a look ?

woprandi avatar Mar 31 '22 14:03 woprandi

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

felangel avatar Apr 20 '24 03:04 felangel

@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

woprandi avatar Apr 29 '24 09:04 woprandi