I was using mocktail for my unit testing and I could not find a way to throw an Exception on the first call but later, on the second call, answer with the right output.
I could find this solution for two different answers but this was not enough for testing exceptions thrown. So I needed to come up with a modified solution.
This is what I wanted to achieve. By the way, I was testing a connection retry with http get.
import 'package:http/http.dart' as http;
import 'package:mocktail/mocktail.dart';
class MockClient extends Mock implements http.Client {}
final mockClient = MockClient();
//First time fails, second one retrieves a result. This doesn't work on Mocktail
when(() => mockClient.get(Uri.parse(url)))
.thenThrow(SocketException()) // Call 1
.thenAnswer((_) => Future.value(http.Response("page content", 200)) // Call 2
);
After trying different ideas, a possible solution was to store each answer inside of a List
import 'package:http/http.dart' as http;
import 'package:mocktail/mocktail.dart';
class MockClient extends Mock implements http.Client {}
final mockClient = MockClient();
final List<Future<http.Response> Function(Invocation)> answers = [
(_) => throw SocketException(),
(_) => Future.value(http.Response("page content", 200)),
];
when(() => mockClient.get(Uri.parse(url)))
.thenAnswer((invocation) => answers.removeAt(0)(invocation));
// Calling answers.removeAt(0) without the lambda method returns the same answer on all of them
In this example Invocation is not used and checks only 2 consecutive calls. But that behavior can be extended from what you can see here.
I haven't tried this with Mockito but it should work in a similar way adapting the syntax.
Related
I tried to mock database to test my local api, i search in official document finding mockito which can work with remote api fine, but also can not work with local database out of box, is there any way to work around of it?
In these cases, you have two options (among many others). Even if my examples assume you're making HTTP calls, it doesn't matter. You can use these strategies regardless the specific use case I'm exposing!
The first one is using the "Strategy pattern" to create an interface for the API and then switch between a test and a production API. Here's a simple example:
abstract class HttpRepository {
const HttpRepository();
Future<Something> sendRequest();
}
You can now create 2 concrete classes: one is for the actual API call and the other is just a mock for tests.
/// Use this in your tests
class MockHttpRepository extends HttpRepository {
const MockHttpRepository();
#override
Future<Something> sendRequest() async {
// Simulating the HTTP call
await Future.delayed(const Duration(seconds: 2));
return Something();
}
}
/// Use this in your Flutter code to make the actual HTTP call or whatever else
class ApiHttpRepository extends HttpRepository {
const ApiHttpRepository();
#override
Future<Something> sendRequest() async {
// Doing a real HTTP call
final response = await makeGetOrPost();
return Something.withData(response);
}
}
In this way, you'll use ApiHttpRepository in your Flutter app and MockHttpRepository in tests. Use const constructors whenever possible.
The other way is using mocks to simulate fake HTTP calls or anything else. Basically, you're using when to "trap" a method call and return a fake response you can control.
// 1. "Enable" mocking on your type
class MockRepo extends Mock implements ApiHttpRepository {}
// 2. Mock methods
const ApiHttpRepository repo = MockRepo();
when(repo.sendRequest()).thenAnswer((_) async => Something());
In this case, we're using thenAnswer because the return type of sendRequest() is of type Future<T>. In your case, if you are reading data from a database you just need to:
Make your class "mockable" using extends Mock implements YourClass
Use when on the mockable instance and control the output
Make sure to use thenAnswer if the method returns a Future<T> and thenReturn in all the other cases.
I am developing an android / ios application in flutter, and I have chosen to use redux for my state management.
I am writing unit tests for my redux actions, which have been implemented using the async_redux package.
I am following the excellent guidelines set out for testing by the author of the package, but I am not sure how to mock the dispatch of further actions from my action under test.
For example, the below LogoutAction dispatchs a DeleteDatabaseAction and waits for it to complete:
class LogoutAction extends ReduxAction<AppState> {
#override
Future<AppState> reduce() async {
await dispatchFuture(DeleteDatabaseAction());
return AppState.initialState();
}
}
class DeleteDatabaseAction extends ReduxAction<AppState> {
#override
FutureOr<AppState> reduce() {
throw StateError(
'Unwanted call to runtime implementation of DeleteDatabaseAction',
);
}
}
void main() {
final store = Store<AppState>(initialState: AppState(loggedIn: true));
final storeTester = StoreTester.from(store);
test('Logout action should return correct state and not throw StateError', () async {
storeTester.dispatch(LogoutAction());
TestInfo<AppState> info = await storeTester.wait(LogoutAction);
expect(info.state.loggedIn, false);
});
}
I want to test only the action under test, and stub out all further action calls.
i.e. How can I mock / stub the dispatch and dispatchFuture methods on ReduxAction, so that the runtime DeleteDatabaseAction implementation is not run?
So far I have attempted:
Inject DeleteDatabaseAction using get_it and inject a mock during test
I have 100+ actions that will now need to be added to my context
Some actions have parameters that change based on where they are called from, so cannot be registered at app startup
Subclass Store, override the above methods and use the subclass in my test here final store = Store<AppState>(initialState: AppState(loggedIn: true))
I will not be able to dispatch my action under test, as it uses the same store in the async_redux test implementation
Here: storeTester.dispatch(LogoutAction());
Create a separate Dispatcher implementation, inject this and override with a mock during tests
This will work, but it is new framework, I can go this route but now I am deviating from the well documented framework provided by asyn_redux
This wasn't available when you asked this question. But now the answer is here: https://pub.dev/packages/async_redux#mocking-actions-and-reducers
To mock an action and its reducer, start by creating a MockStore in your tests, instead of a regular Store. The MockStore has a mocks parameter which is a map where the keys are action types, and the values are the mocks. For example:
var store = MockStore<AppState>(
initialState: initialState,
mocks: {
MyAction1 : ...
MyAction2 : ...
...
},
);
However, there are other ways:
As you said, use get_it or some other dependency injection code to inject the http client into the action reducer. This works well.
Use a DAO. For example:
class DeleteDatabaseAction extends ReduxAction<AppState> {
#override
Future<AppState> reduce() {
await dao.deleteDatabase();
return null;
}
Then you can mock the DAO itself, or inject the DAO via get_it. You can also make the dao a getter to some BaseAction (that extends ReduxAction) and inject it there.
I am newbie in Flutter as well as TDD and I do not understand why and when to mark unit test as async in flutter.
Looking through the documentation I found this code snippet:
// Create a MockClient using the Mock class provided by the Mockito package.
// Create new instances of this class in each test.
class MockClient extends Mock implements http.Client {}
main() {
group('fetchPost', () {
test('returns a Post if the http call completes successfully', () async {
final client = MockClient();
// Use Mockito to return a successful response when it calls the
// provided http.Client.
when(client.get('https://jsonplaceholder.typicode.com/posts/1'))
.thenAnswer((_) async => http.Response('{"title": "Test"}', 200));
expect(await fetchPost(client), const TypeMatcher<Post>());
});
test('throws an exception if the http call completes with an error', () {
final client = MockClient();
// Use Mockito to return an unsuccessful response when it calls the
// provided http.Client.
when(client.get('https://jsonplaceholder.typicode.com/posts/1'))
.thenAnswer((_) async => http.Response('Not Found', 404));
expect(fetchPost(client), throwsException);
});
});
}
If you look carefully you will noticed that first test is marked as async and the second is not. Why is that? What is different between these two test(except the cases) so that the first one has to be async?
Thanks :)
When you want to use await, you have to mark a callback or function in general as async.
In your case:
expect(await fetchPost(client), const TypeMatcher<Post>());
The await is needed because the result of the function execution matters. They are expecting exactly a Post type to be returned, hence, they need the await.
In the other case:
expect(fetchPost(client), throwsException);
It only matters that an exception is thrown, but the result is irrelevant.
When to mark callback with async when testing
Whenever you need await, you mark your callbacks with async. In general, I would advise always awaiting functions in tests because the tests will otherwise run in parallel, which could show undesired behavior.
I'm making an example with flutter and I've come across a question mark. In my project I have implemented dependency injection and I have two classes to get data one for production and testing with local data (Mock). The problem is that the local data I have stored in a json file and when I implement the functionality "fetchProducts" I do not know how to get the Context to load the json... I hope you can help me, thanks.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:graphqllapp/data/product_data.dart';
import 'package:path/path.dart';
class MockProductRepository implements ProductRepository {
#override
Future<List<Product>> fetchProducts() async {
// TODO: implement fetchUsers
String data = await
DefaultAssetBundle.of(????).loadString("mockdata/data.json");
var jsonResult = json.decode(data);
return new Future.value(products);
}
}
You can instead use rootBundle which is the default value of DefaultAssetBundle
rootBundle.loadString("mockdata/data.json");
I'm trying to cached the Unauthorized result from the auth builder, but i can't find a way to properly request the result from cache (without using redirect with reverse route) any idea? this is my code:
object Authenticated extends AuthenticatedBuilder(
request =>
request.session.get("email"),
request =>
Unauthorized(html.index("fail!", loginForm))
)
I want to do something like:
object Authenticated extends AuthenticatedBuilder(
request =>
request.session.get("email"),
request =>
Cached("Fail") { Action { implicit request =>
Unauthorized(html.index("fail!", loginForm))
}
}
)
this will of course return Cached instead of simpleResult and fail...
btw i'm using play 2.2.1
Thanks to #Peter i was able to return a SimpleResult with getOrElse, my final code is a bit more complex but here is an example that hopefully will help more people:
import play.api.cache.Cache
import play.api.Play.current
import play.api.mvc.Security.AuthenticatedBuilder
object Authenticated extends AuthenticatedBuilder(
request =>
request.session.get("email"),
request =>
Cache.getOrElse("UnAuth"){
Unauthorized("Fail")
}
)