I am trying to mock Dio's get method. The mock is working fine as per my test. However, when calling inside the test type 'Null
' is not a subtype of type 'Future<Response<dynamic>>
'.
I have called newsApi.get('/top-headlines')
during test as well. And, I can assure it that the mock is returning data fine. But for unknown reasons, the call inside NewsService
is giving null
. Could you please guide me in solving the issue?
Filename: services/news.dart
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:newsapp/enums/news_category.dart';
import 'package:newsapp/enums/news_country.dart';
import 'package:newsapp/models/articles.dart';
import 'package:newsapp/models/error.dart';
import '../main.dart';
class NewsService {
final Dio newsApi;
NewsService({required this.newsApi});
Future<dynamic> getArticlesByCategory(
NewsCategory category, {
int page = 1,
int pageSize = 100,
NewsCountry country = NewsCountry.US,
}) async {
final response = await newsApi.get('top-headlines', queryParameters: {
'category': category.name,
'country': country.name.toLowerCase(),
'page': page,
'pageSize': pageSize,
});
print(response);
await newsApi.get('top-headlines', queryParameters: {
'category': category.name,
'country': country.name.toLowerCase(),
'page': page,
'pageSize': pageSize,
}).then((response) {
if (response.statusCode == HttpStatus.ok) {
if (response.data['status'] == 'ok') {
return Articles.fromJson(response.data);
} else {
return Error.fromJson(response.data);
}
} else if (response.statusCode == HttpStatus.unauthorized) {
return Error.fromJson(response.data);
} else {
return Future.error(
'Failure processing request. Please try again later.');
}
}, onError: (error) {
print(error);
logger.e(error);
return Future.error(error);
}).catchError((error) {
print(error);
logger.e(error);
return error;
});
}
}
Filename: test/news.dart
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:newsapp/enums/news_category.dart';
import 'package:newsapp/models/articles.dart';
import 'package:newsapp/services/news.dart';
import '../mocks/dio.dart';
void main() async {
group('NewsService tests', () {
//Arrange
late MockDio newsApi;
group('NewsService.getArticles() tests', () {
setUp(() {
newsApi = MockDio();
Future<Response> responseMethod = Future.value(Response(
data: {
"status": "ok",
"totalResults": 11207,
"articles": [
{
"source": {"id": "bbc-news", "name": "BBC News"},
"author": "https://www.facebook.com/bbcnews",
"title": "Indian PM Modi's Twitter hacked with bitcoin tweet",
"description":
"The Indian prime minister's account had a message stating that bitcoin would be distributed to citizens.",
"url": "https://www.bbc.co.uk/news/world-asia-india-59627124",
"urlToImage":
"https://ichef.bbci.co.uk/news/1024/branded_news/5998/production/_122063922_mediaitem122063921.jpg",
"publishedAt": "2021-12-12T10:59:57Z",
"content":
"Image source, AFP via Getty Images\r\nImage caption, Modi has has more than 70 million Twitter followers\r\nIndian Prime Minister Narendra Modi's Twitter account was hacked with a message saying India ha… [+854 chars]"
},
{
"source": {"id": null, "name": "New York Times"},
"author": "Corey Kilgannon",
"title": "Why New York State Is Experiencing a Bitcoin Boom",
"description":
"Cryptocurrency miners are flocking to New York’s faded industrial towns, prompting concern over the environmental impact of huge computer farms.",
"url":
"https://www.nytimes.com/2021/12/05/nyregion/bitcoin-mining-upstate-new-york.html",
"urlToImage":
"https://static01.nyt.com/images/2021/11/25/nyregion/00nybitcoin5/00nybitcoin5-facebookJumbo.jpg",
"publishedAt": "2021-12-06T00:42:28Z",
"content":
"The plant opening northeast of Niagara Falls this month, in Somerset, N.Y., is part of a \$550 million project by Terawulf, a Bitcoin mining company. The project also includes a proposed 150-megawatt … [+1514 chars]"
}
]
},
statusCode: HttpStatus.ok,
requestOptions: RequestOptions(path: '/top-headlines')));
when(() => newsApi.get(
'/top-headlines',
queryParameters: any(named: 'queryParameters'),
options: any(named: 'options'),
cancelToken: any(named: 'cancelToken'),
onReceiveProgress: any(named: 'onReceiveProgress'),
)).thenAnswer((_) => responseMethod);
});
tearDown(() {
reset(newsApi);
});
test('Get Articles', () async {
// Arrange
NewsService newsService = NewsService(newsApi: newsApi);
final response = await newsApi.get('/top-headlines');
print('Response');
print(response.data);
final articles = Articles.fromJson(response.data);
print(articles);
// Act
await newsService.getArticlesByCategory(NewsCategory.business);
// Assert
verify(() => newsApi.get('/top-headlines',
queryParameters: any(named: 'queryParameters'))).called(1);
});
});
});
}
Finally I've got a working solution.
It seems that Dio needs a seperate mock library to mock itself using adapters. The library to use is http_mock_adapter: ^0.1.4
and its below is its link.
LINK: Http Mock Library
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';
import 'package:test/test.dart';
void main() async {
// How to mock with DioAdapter
group('DioAdapter usage', () {
// Creating dio instance for mocking.
// For instance: you can use your own instance from injection and replace
// dio.httpClientAdapter with mocker DioAdapter
const path = 'https://example.com';
test('Expects Dioadapter to mock the data', () async {
final dio = Dio();
final dioAdapter = DioAdapter();
dio.httpClientAdapter = dioAdapter;
dioAdapter
.onGet(path)
.reply(200,
{'message': 'Successfully mocked GET!'}) // only use double quotes
.onPost(path)
.reply(200, {'message': 'Successfully mocked POST!'});
// Making dio.get request on the path an expecting mocked response
final getResponse = await dio.get(path);
expect(jsonEncode({'message': 'Successfully mocked GET!'}),
getResponse.data);
// Making dio.post request on the path an expecting mocked response
final postResponse = await dio.post(path);
expect(jsonEncode({'message': 'Successfully mocked POST!'}),
postResponse.data);
});
// Alternatively you can use onRoute chain to pass custom requests
test('Expects Dioadapter to mock the data with onRoute', () async {
final dio = Dio();
final dioAdapter = DioAdapter();
dio.httpClientAdapter = dioAdapter;
dioAdapter
.onRoute(path, request: Request(method: RequestMethods.PATCH))
.reply(200, {
'message': 'Successfully mocked PATCH!'
}) // only use double quotes
.onRoute(path, request: Request(method: RequestMethods.DELETE))
.reply(200, {'message': 'Successfully mocked DELETE!'});
// Making dio.get request on the path an expecting mocked response
final patchResponse = await dio.patch(path);
expect(jsonEncode({'message': 'Successfully mocked PATCH!'}),
patchResponse.data);
// Making dio.post request on the path an expecting mocked response
final deleteResposne = await dio.delete(path);
expect(jsonEncode({'message': 'Successfully mocked DELETE!'}),
deleteResposne.data);
});
});
// Also, for mocking requests, you can use dio Interceptor
group('DioInterceptor usage', () {
// Creating dio instance for mocking.
// For instance: you can use your own instance from injection and add
// DioInterceptor in dio.interceptors list
final dioForInterceptor = Dio();
final dioInterceptor =
DioInterceptor(); // creating DioInterceptor instance for mocking requests
dioForInterceptor.interceptors.add(dioInterceptor);
const path = 'https://example2.com';
test('Expects Dioadapter to mock the data', () async {
// Defining request types and their responses respectively with their paths
dioInterceptor
.onDelete(path)
.reply(200,
{'message': 'Successfully mocked GET!'}) // only use double quotes
.onPatch(path)
.reply(200, {'message': 'Successfully mocked POST!'});
// Making dio.delete request on the path an expecting mocked response
final getResponse = await dioForInterceptor.delete(path);
expect(jsonEncode({'message': 'Successfully mocked GET!'}),
getResponse.data);
// Making dio.patch request on the path an expecting mocked response
final postResposne = await dioForInterceptor.patch(path);
expect(jsonEncode({'message': 'Successfully mocked POST!'}),
postResposne.data);
});
});
group('Raising the custrom Error onRequest', () {
const path = 'https://example.com';
test('Test that throws raises custom exception', () async {
final dio = Dio();
final dioAdapter = DioAdapter();
dio.httpClientAdapter = dioAdapter;
const type = DioErrorType.RESPONSE;
final response = Response(statusCode: 500);
const error = 'Some beautiful error';
// Building request to throw the DioError exception
// on onGet for the specific path
dioAdapter.onGet(path).throws(
500,
DioError(
type: type,
response: response,
error: error,
),
);
// Checking that exception type can match `AdapterError` type too
expect(() async => await dio.get(path),
throwsA(TypeMatcher<AdapterError>()));
// Checking that exception type can match `DioError` type too
expect(() async => await dio.get(path), throwsA(TypeMatcher<DioError>()));
// Checking the type and the message of the exception
expect(
() async => await dio.get(path),
throwsA(
predicate((DioError e) => e is DioError && e.message == error)));
});
});
}