mockito
mockito copied to clipboard
Mock functions?
Don't really know how or if this is achievable but it would be great if there's some magical means to create a mock function rather than a mock class that can be passed into things expecting any sort of typedefs or function signatures and then verify its (dynamic) calls and args.
A concrete example what you're trying to accomplish?
I'm not really proficient with mocks, but you should be able to mock the call
method. The problem is having the correct function type.
A Dart object can only have one function type at a time (the type of the call
method of its interface), so you can't have an object that has "any" function type and passes any is SomeTypedef
test. You can make your mock implement a class that has a particular call
method, and that makes it a function of that type, but only that type.
Example:
typedef A SomethingIWantToMock(B b, C c);
void somethingIWantToTest(D d, E e, SomethingIWantToMock mock) {
[the code to test...]
A a = mock(something, something);
[more code to test...]
}
test('check that somethingIWantToTest interacts correctly with SomethingIWantToMock', () {
SomethingIWantToMock mock = [make a mock...];
when(mock).thenAnswer((Invocation invocation) => [use b, use c]);
somethingIWantToTest(mock);
assert
assert
verify(mock)
verify(mock)
});
More or less the same as class mocking but with functions.
cc @cbracken FYI
I think the main issue here is that it won't be possible to create a single "Mock" function that could represent (statically) any function. In this particular case, it might honestly be simpler to just create a stub implementation of your function:
test('...', () {
void stubDoSomething(a, b, c) {
// ...
}
};
Hi @xster yeah this is, with the current implementation, impossible ☹️ . When you wrap a method call with when(...)
, mockito does literally call the method, but it routes directly to the noSuchMethod method of Mock. We have no way of routing a call of a top-level method.
I think @matanlurey 's suggestion should work perfectly if you get to inject the function into somethingIWantToTest
.
If you want to just create a mocked function to use as onTap on widget test environment, with mockito you can do something like that:
abstract class MyFunction {
void call();
}
class MyFunctionMock extends Mock implements MyFunction {}
Test:
void main() {
final mock = MyFunctionMock();
...
// This is inside the the testWidgets callback
await tester.pumpWidget(
widgetToBeTested(
item: registeredListItem,
onTap: mock,
),
);
//Logic to tap the widgetToBeTested
verify(mock()).called(1);
}
It's now very easy to mock functions. You just need to mock an abstract class that contains all the functions you need as methods, and then pass the methods as the needed functions.
Here's a full example:
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'example_test.mocks.dart';
/// The function to test
Future<DateTime> getCurrentTime(
Future<String> Function(String) postRequest) async {
String r = await postRequest('getCurrentTime');
return DateTime.fromMillisecondsSinceEpoch(int.parse(r) * 1000, isUtc: true);
}
abstract class MyFuncs {
Future<String> postRequest(String name);
}
@GenerateMocks([MyFuncs])
void main() {
test('getCurrentTime', () async {
final myFuncs = MockMyFuncs();
when(myFuncs.postRequest('getCurrentTime'))
.thenAnswer((_) async => '1647249645');
final dt = await getCurrentTime(myFuncs.postRequest);
expect(dt, DateTime.utc(2022, 3, 14, 9, 20, 45));
});
}
@srawlins - I just saw the pattern of mocking an abstract class for it's tearoffs used in a code review and it surprised me at first.
Do you think it would be reasonable to more directly support mocking callbacks now that we have codegen? We could feasibly turn Future<String> Function(String)
into a full mock class with a method that could be torn off, without asking the user to write the abstract class around it too.
A few points to consider:
- How would we name them? Maybe we'd need to configure them in a map keyed by a name?
{'postRequest': Future<String> Function(String)}
- How would we surface them? We don't necessarily need to expose the fact that they are tearoffs of a regular
Mock
Future<String> Function(String) mockPostRequest() => _MockPostRequest().call;
This is a very cool idea. Something like MockSpec<Future<String> Function(String)>(as: #mockPostRequest)
.
About this point, I have a slightly different problem that I don't know how to solve myself:
// application/repository/todo.dart
import 'package:flutter/foundation.dart';
import 'package:port_domain/networking.dart';
import 'package:todo/domain/todo.dart';
class TodoRepository {
final RestClient _client;
final String _baseUrl;
List<Todo> todos = [];
TodoRepository(this._client, this._baseUrl);
Future<void> fetchFromRemote() async {
Response groupRes = await _client.get(
_baseUrl + 'api/todos',
parser: (data) => compute(Todo.fromJsonList, data as List),
);
if (groupRes is Success) {
todos.clear();
todos.addAll(groupRes.result);
}
}
}
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:port_domain/networking.dart';
import 'package:todo/application/repository/todo.dart';
@GenerateMocks([RestClient])
import 'repository_todo_test.mocks.dart';
void main() {
late RestClient client;
late TodoRepository repository;
setUp(() {
client = MockRestClient();
repository = TodoRepository(client, '');
});
group('TodoRepository', () {
test('should have entities available after a successful fetch', () async {
var todoList = [const Todo(id: 1)];
when(
client.get(
'api/todos',
parser: any, // <-- This fails because any isn't supported
),
).thenAnswer((_) async => Success<List<Todo>>(todoList));
await repository.fetchFromRemote();
expect(repository.todos, equals(todoList));
});
});
}
Can someone point me out to a way to test it? The problem here is that the Repository creates a closure that should be identified by mockito as the same function.
@neokree You have to use anyNamed('parser')
here. That's a limitation of Mockito implementation.
Regarding adding support for mocking functions: definitely doable with codegen, but not sure it's a high priority for us. Will be happy to review changes.