Why is there no spy functionality in Mockito Dart? - dart

The following code is a simplified example from my code. I have class A which is dependent on class B. I want to test class A, so I mock class B. Then I'm writing a test for a method of class A and inside of that test I write a stub for whenever a method from my mocked class B is called:
fetchData() async {
try {
await b.getData();
} on DioError catch (e) {
switch (e.response!.statusCode) {
case 401:
logout();
throw UnauthorizedException();
default:
throw UnspecifiedDioException(error: e);
}
}
Test written for fetchData() method:
test('check if fetchData calls logout when 401 is returned', () {
when(mockB.getData())
.thenAnswer((_) async =>
throw DioError(
requestOptions: RequestOptions(path: ""),
response: Response(requestOptions: RequestOptions(path: ""), statusCode: 401)));
verify(a.logout()); // doesn't work because A isn't mocked
});
I've read that you can do this very easily with spies but to my surprise spies are available for every language which uses mockito except for dart. It's apparently deprecated but then again how can something be deprecated if there isn't even a newer version to replace it with.
I'd really appreciate it if someone could tell me if there is a convenient workaround for what I'm trying to achieve. Thanks in advance.
Edit: I've changed the question because the former one wasn't making much sense. I just wanna know if there is something like spies in dart or not.

Using mocktail..
You should stub your logout invocation's dependency as well.
class A {
A({required this.api, required this.auth});
// to be mocked
final Api api;
final Auth auth;
Future<void> fetchData() async {
try {
await api.getData();
} catch (e) {
auth.logout();
}
}
}
class Auth {
Future<void> logout() => Future(() {});
}
class Api {
Future<void> getData() => Future(() {});
}
And your test
class MockApi extends Mock implements Api {}
class MockAuth extends Mock implements Auth {}
void main() {
// create mock objects
final mockApi = MockApi();
final mockAuth = MockAuth();
test('when [Api.getData] throws, [Auth.logout] is called', () async {
// create an instance of "A" and use your mock objects
final a = A(api: mockApi, auth: mockAuth);
// use "thenThrow" to throw
when(() => mockApi.getData()).thenThrow('anything');
// use "thenAnswer" for future-returning methods
when(() => mockAuth.logout()).thenAnswer((_) => Future.value(null));
// call the method to "start" the test
await a.fetchData();
// verify logout was called
verify(mockAuth.logout).called(1); // passes
});
}

Related

How to init provider?

The case is: i am going to use MyProvider in the following way SomeProvidersClass.get().myProvider; from this structure:
class SomeProvidersClass {
late final myProvider =
Provider((ref) => MyProvider());
MyProvider get myProvider =>
read(providers.myProvider);
}
But
i heed to MyProvider be initialized via some async function;
so i tried to do it in the following way:
class MyProvider {
late final SomeType _someVariable; // need to init in async way
Future<void> init() async => _someVariable = await SomeType.getInstance();
MyProvider();
// example of content of this provider that i am going to use
dynamic? getSmth() => _someVariable.getSmth();
}
and i got the following error
LateInitializationError: field has not been initialized;
Could you suggest me any approach to do it? i am new in riverpod and providers :)
Try this:
class MyProvider {
SomeType? _someVariable; // need to init in async way
Future<void> init() async => _someVariable = await SomeType.getInstance();
MyProvider(){
init();
}
// example of content of this provider that i am going to use
dynamic? getSmth() => _someVariable?.getSmth();
}

How can I extend functions in Dart?

I know about the extension feature in Dart, but how can I use it with functions?
Essentially, I am facing two problems:
What do I extend on (extension FancyFunction on ?)?
I would want to add a function like toAsync that makes the function return a Future of its usual result.
How would I implement calling?
I could create a callAsync member that executes this() or this.call(), but I would like to use regular calling syntax, i.e. just parentheses.
What do I extend on when extending functions?
Dart has a Function type. This can be extended on and you can pass type parameters if you want.
Here is an example from the changelog:
extension CurryFunction<R, S, T> on R Function(S, T) { ... }
Furthermore, you can extend any typedef of a function.
For adding the toAsync and callAsync functionality a generic return type R will do. Note that this will only extend functions without parameters as Function() takes no parameters:
extension FancyFunction<R> on R Function() {
Future<R> Function() toAsync() => () async => this();
}
Now, this could be used like this:
void syncHello() => print('Hello');
void main() {
final asyncHello = syncHello.toAsync();
asyncHello().then((_) => print(', World!'));
}
How do I implement calling?
Every class in Dart can implement the call method. You can either execute this method simply using parentheses or with .call().
Here is an example implementation:
extension FancyFunction<R> on R Function() {
Future<R> call() async => this();
}
Since every Function already implements call, the extension member cannot be called implicitly.
Instead, you will have to explicitly declare your function as a FancyFunction to be able to call it:
void syncHello() => print('Hello');
void main() {
FancyFunction(syncHello)()
.then((_) => print(', World!'));
}
Note that FancyFunction(syncHello)() is the same method call as FancyFunction(syncHello).call().
However, now we have two problems:
We have to explicitly declare our function to be a FancyFunction, which somewhat defeats the purpose of having an extension.
The call method will always return a Future as the regular call method cannot be accessed anymore when we declare a FancyFunction.
Instead, adding a method like callAsync seems like a better solution:
extension FancyFunction<R> on R Function() {
Future<R> callAsync() async => this();
}
Now, callAsync can be used implicitly again:
void syncHello() => print('Hello');
void main() {
syncHello.callAsync()
.then((_) => print(', World!'));
syncHello(); // regular call
}

How to access one class method from another class in dart?

I'm new to dart. Currently, working on a mobile app through flutter. I have a Helper class which has some common methods which I've planned throughout the app. I've included that Helper class in another class. But, can't able to fig. out how to access its methods.
My commom Helper class code:
import 'dart:async';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class Helper {
Map userDetails = {};
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
// --- Method for getting user details from shared preference ---
Future<Map>getUserDetailsFromSharedPreference () async {
try {
final SharedPreferences prefs = await _prefs;
if(prefs.getString('user') != null) {
this.userDetails = json.decode(prefs.getString('user'));
} else {
print('Shared preference has no data');
}
} catch (e){
print('Exception caught at getUserDetails method');
print(e.toString());
}
return this.userDetails;
}
}
Here is my main program code where I've included the Helper class & trying to access it's getUserDetailsFromSharedPreference (). In this case, I'm getting an error like Only static memebers can be accessed in initializers. I also tried to extends Helper class in UserProfile class. But, there also I'm getting a different kind of errors. Can't able to identify how to do this thing.
import 'package:flutter/material.dart';
import 'helper.dart';
class UserProfile extends StatefulWidget {
#override
UserProfileState createState() => new UserProfileState();
}
class UserProfileState extends State<UserProfile> {
Helper helper = new Helper();
var userData = helper.getUserDetailsFromSharedPreference();
}
#Günter Zöchbauer I've made my Helper.dart file like this as you've suggested -
import 'dart:async';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class Helper {
Map userDetails = {};
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
static Helper _instance;
factory Helper() => _instance ??= new Helper._();
Helper._();
// --- Method for getting user details from shared preference ---
Future<Map>getUserDetailsFromSharedPreference () async {
try {
final SharedPreferences prefs = await _prefs;
if(prefs.getString('user') != null) {
this.userDetails = json.decode(prefs.getString('user'));
} else {
print('Shared preference has no data');
}
} catch (e){
print('Exception caught at getUserDetails method');
print(e.toString());
}
return this.userDetails;
}
}
Now, in my tryint to access that getUserDetailsFromSharedPreference() method I'm getting the same error Only static memebers can be accessed in initializers .
You could ensure a singleton instance of the class using a public factory constructor with a private regular constructor:
class Helper {
static Helper _instance;
factory Helper() => _instance ??= new Helper._();
Helper._();
...
}
If you call new Helper(), you'll always get the same instance.
You need to import the file that contains class Helper {} everywhere where you want to use it.
??= means new Helper._() is only executed when _instance is null and if it is executed the result will be assigned to _instance before it is returned to the caller.
update
getUserDetailsFromSharedPreference is async and can therefore not be used in the way you use it, at least it will not lead to the expected result. getUserDetailsFromSharedPreference returns a Future that provides the result when the Future completes.
class UserProfileState extends State<UserProfile> {
Helper helper = new Helper();
Future<Map> _userData; // this with ??= of the next line is to prevent `getUserDetailsFromSharedPreference` to be called more than once
Future<Map> get userData => _userData ??= helper.getUserDetailsFromSharedPreference();
}
If you need to access userData you need to mark the method where you do with async and use await to get the result.
foo() async {
var ud = await userData;
print(ud);
}
To access other class method you can simply put static on the method.
class Helper {
static printing(String someText){
print(someText);
}
}
void main() {
Helper.printing('Hello World!');
}
I think this question is more related to accessing one class data in another class. So I explained on the basis of my understanding of the question but if I'm not correct about it.
but if you want to access data of class A, not directly but through class B.
so first you have to make an object of A in class B but remember one thing you would have to make the object static in order to get access to the data of class A within Class B
If you still are confused about all this, I made a solution video.
Check it out: https://youtu.be/shK7ZraruCI

How to write tests using the service scope

My app is accessing object from the service scope (package:gcloud/service_scope.dart), like the storageService and additional services that I put inside the scope with ss.register().
Now I want to unit test a function that accesses this scope, and uses mock objects that I want to put in the service scope.
Is the only way to do so, to register them for every test, like this:
var withServiceScope = (callback()) => ss.fork(() {
// Register all services here
return callback();
});
test('the description', () => withServiceScope(() async {
// Call my function that can now access the service scope
}));
Or is there are way that allows me to do that in the setUp() function so I don't need to add this line for each test?
This might make it simpler to write your tests (code not tested)
import 'package:test/test.dart' as t;
import 'package:test/test.dart' show group;
var withServiceScope = (callback()) => ss.fork(() {
// Register all services here
return callback();
});
test(String description, Function testFunction) {
t.test(description, () => withServiceScope(() async {
testFunction();
}));
}
main() {
test('the description', () async {
// Call my function that can now access the service scope
}));
}

Dart - how to mock a method that returns a future

I have a class that defines a method that returns a Future. The Future contains a list of class that also return a future.
class User{
Future<List<Album>> albums(){
};
}
class Album{
Future<List<Photos>> photos(){
}
};
What is the best way to mock the method in these classes when testing another class?
The class I am trying to test looks a bit like
class Presenter {
Presenter( User user){
user.albums().then( _processAlbums);
}
_processAlbums(List<Album> albums) {
albums.forEach( (album)=>album.photos.then( _processPhotos));
}
_processPhotos(List<Photo> photos) {
....stuff
}
}
I tried writing a unit test like this
class MockUser extends Mock implements User{}
class MockAlbum extends Mock implements Album{}
class MockPhoto extends Mock implements Photo{}
class MockFutureList<T> extends Mock implements Future<T>{
MockFutureList( List<T> items){
when( callsTo( "then")).thenReturn( items);
}
}
void main(){
test("constuctor should request the albums from the user ",(){
MockUser user = new MockUser();
MockAlbum album = new MockAlbum();
List<Album> listOfAlbums = [ album];
MockPhoto photo = new MockPhoto();
List<Album> listOfPhotos = [ album];
user.when( callsTo( "albums")).thenReturn( new MockFutureList(listOfAlbums));
album.when( callsTo( "photos")).thenReturn( new MockFutureList( listOfPhotos));
PicasaPhotoPresentor underTest = new PicasaPhotoPresentor( view, user);
user.getLogs( callsTo( "albums")).verify( happenedOnce);
album.getLogs( callsTo( "photos")).verify( happenedOnce);
});
}
This allowed me to test that the constructor called the user.photos() method, but not that the album.photos() method was called.
I am not sure that mocking a Future is a good idea - Would it not be better to create a 'real' Future that contains a list of Mocks?
Any ideas would be very helpful!
Since you're only interested in verifying that methods in User and Album are called, you won't need to mock the Future.
Verifying the mocks gets a bit tricky here, because you're chaining futures inside the constructor. With a little understanding of how the event loop works in Dart, I recommend using a future and calling expectAsync after you create your presenter.
The expectAsync function tells the unit test library to wait until it's called to verify your tests. Otherwise the test will complete successfully without running your expectations.
With this, here's what your test should would look like:
import 'package:unittest/unittest.dart';
class MockUser extends Mock implements User {}
class MockAlbum extends Mock implements Album {}
void main() {
test("constuctor should request the albums from the user ", () {
var user = new MockUser();
var album = new MockAlbum();
user.when(callsTo("albums")).thenReturn(new Future(() => [album]));
var presenter = new PicasaPhotoPresentor(view, user);
// Verify the mocks on the next event loop.
new Future(expectAsync(() {
album.getLogs(callsTo("photos")).verify(happendOnce);
}));
});
}
Here is how I managed to do it
1) Define FutureCallbackMock
class FutureCallbackMock extends Mock implements Function {
Future<void> call();
}
2) get function from a mock and set it up
FutureCallback onPressed = FutureCallbackMock().call;
completer = Completer<void>();
future = completer.future;
when(onPressed()).thenAnswer((_) => future);
3) Verify like so
verify(onPressed()).called(1);
4) Complete the future if needed:
completer.complete();
NOTE: in flutter tests I had to wrap my test in tester.runAsync like so
testWidgets(
'when tapped disables underlying button until future completes',
(WidgetTester tester) async {
await tester.runAsync(() async {
// test here
});
});
I was able to do this with Mocktail. This is the article that this is from, and explains how to integrate it into your app. This is a full widget test and depends on this gist code.
The crux is that you need to declare a Mock class that has a call method. Then, you can then mock the top-level function that returns a Future. You can use the when and verify methods with this.
//Gist code
import 'package:gist/main.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/material.dart';
class LaunchMock extends Mock {
Future<bool> call(
Uri url, {
LaunchMode? mode,
WebViewConfiguration? webViewConfiguration,
String? webOnlyWindowName,
});
}
void main() {
testWidgets('Test Url Launch', (tester) async {
//These allow default values
registerFallbackValue(LaunchMode.platformDefault);
registerFallbackValue(const WebViewConfiguration());
//Create the mock
final mock = LaunchMock();
when(() => mock(
flutterDevUri,
mode: any(named: 'mode'),
webViewConfiguration: any(named: 'webViewConfiguration'),
webOnlyWindowName: any(named: 'webOnlyWindowName'),
)).thenAnswer((_) async => true);
final builder = compose()
//Replace the launch function with a mock
..addSingletonService<LaunchUrl>(mock);
await tester.pumpWidget(
builder.toContainer()<MyApp>(),
);
//Tap the icon
await tester.tap(
find.byIcon(Icons.favorite),
);
await tester.pumpAndSettle();
verify(() => mock(flutterDevUri)).called(1);
});
}

Resources