mocktail
mocktail copied to clipboard
How to mock with await for ?
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.
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 well, what's the problem with the code I posted ?
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.
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
@felangel Were you able to take a look ?
@woprandi is this still an issue? This fell off my radar, apologies :(
@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