Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mock functions? #62

Open
xster opened this issue Apr 25, 2017 · 13 comments
Open

Mock functions? #62

xster opened this issue Apr 25, 2017 · 13 comments
Labels
contributions-welcome Contributions welcome to help resolve this (the resolution is expected to be clear from the issue) P3 A lower priority bug or feature request type-enhancement A request for a change that isn't a bug

Comments

@xster
Copy link

xster commented Apr 25, 2017

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.

@zoechi
Copy link

zoechi commented Apr 26, 2017

A concrete example what you're trying to accomplish?

@lrhn
Copy link
Member

lrhn commented Apr 26, 2017

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.

@xster
Copy link
Author

xster commented Apr 26, 2017

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.

@xster
Copy link
Author

xster commented Apr 26, 2017

cc @cbracken FYI

@matanlurey
Copy link
Contributor

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) {
    // ...
  }
};

@srawlins
Copy link
Member

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.

@manoellribeiro
Copy link

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);

}

@noamraph
Copy link

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));
  });
}

@natebosch natebosch reopened this Jun 3, 2022
@natebosch
Copy link
Member

@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;

@srawlins
Copy link
Member

srawlins commented Aug 4, 2022

This is a very cool idea. Something like MockSpec<Future<String> Function(String)>(as: #mockPostRequest).

@neokree
Copy link

neokree commented Aug 19, 2022

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.

@yanok
Copy link
Contributor

yanok commented Jul 3, 2023

@neokree You have to use anyNamed('parser') here. That's a limitation of Mockito implementation.

@yanok
Copy link
Contributor

yanok commented Jul 3, 2023

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.

@yanok yanok added contributions-welcome Contributions welcome to help resolve this (the resolution is expected to be clear from the issue) P3 A lower priority bug or feature request type-enhancement A request for a change that isn't a bug labels Jul 3, 2023
copybara-service bot pushed a commit that referenced this issue Jul 7, 2023
Towards #62

Update the README and example usage with an example of writing a class to hold
the callback signatures and mocking it with codegen. Demonstrate that the
callbacks can be stubbed after being torn off.

PiperOrigin-RevId: 546011492
copybara-service bot pushed a commit that referenced this issue Jul 10, 2023
Towards #62

Update the README and example usage with an example of writing a class to hold
the callback signatures and mocking it with codegen. Demonstrate that the
callbacks can be stubbed after being torn off.

PiperOrigin-RevId: 546817751
mosuem pushed a commit to dart-lang/test that referenced this issue Oct 17, 2024
Towards dart-lang/mockito#62

Update the README and example usage with an example of writing a class to hold
the callback signatures and mocking it with codegen. Demonstrate that the
callbacks can be stubbed after being torn off.

PiperOrigin-RevId: 546817751
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contributions-welcome Contributions welcome to help resolve this (the resolution is expected to be clear from the issue) P3 A lower priority bug or feature request type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

10 participants