I am trying to use mockito to return a fake response in the http.Client call and be able to test the service. I have followed the documentation. It tells me that I should use annotate to generate a fake class, but it seems that the null safe of flutter is causing problems. Does anyone know how? fix it thanks
movies_provider_test.dart
import 'package:http/http.dart' as http;
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:watch_movie_app/src/data/data_source/remote/http_request.dart';
import 'package:watch_movie_app/src/data/models/models.dart';
import 'package:watch_movie_app/src/domain/services/movie_service.dart';
import 'package:watch_movie_app/src/environment_config.dart';
import 'mocks/popular_movies.dart';
import 'movies_provider_test.mocks.dart';
@GenerateMocks([http.Client])
void main() {
test('returns an movies if the http call completes sucessfully', () async {
final mockHttp = MockClient();
final container = ProviderContainer(
overrides: [
httpClientProvider.overrideWithValue(HttpRequest(httpClient: mockHttp)),
],
);
addTearDown(container.dispose);
final environmentConfig = container.read(environmentConfigProvider);
final movieService = container.read(movieServiceProvider);
String urlApi =
"${environmentConfig.domainApi}/${environmentConfig.apiVersion}/tv/popular?api_key=${environmentConfig.movieApiKey}&language=en-US&page=1";
Uri url = Uri.parse(urlApi);
when(mockHttp.get(url)).thenAnswer(
(_) async => http.Response(fakeMovies, 200),
);
expectLater(await movieService.getMovies(), isInstanceOf<List<Movie>>());
});
}
movie_service.dart
import 'package:http/http.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:watch_movie_app/src/data/data_source/remote/http_request.dart';
import 'package:watch_movie_app/src/domain/enums/enums.dart';
import 'package:watch_movie_app/src/data/models/models.dart';
import 'package:watch_movie_app/src/environment_config.dart';
import 'package:watch_movie_app/src/helpers/movie_api_exception.dart';
final movieServiceProvider = Provider<MovieService>((ref) {
final config = ref.read(environmentConfigProvider);
final httpRequest = ref.read(httpClientProvider);
return MovieService(config, httpRequest);
});
class MovieService {
final EnvironmentConfig _environmentConfig;
final HttpRequest _http;
MovieService(this._environmentConfig, this._http);
Future<List<Movie>> getMovies() async {
try {
String url =
"${_environmentConfig.domainApi}/${_environmentConfig.apiVersion}/tv/popular?api_key=${_environmentConfig.movieApiKey}&language=en-US&page=1";
final response =
await _http.request(typeHttp: EnumHttpType.get, urlApi: url);
if (response.statusCode != 200) {
throw const MovieApiException('Error al consulta las series populares');
}
List<Movie> movies = allMoviesFromJson(response.body);
return movies;
} on ClientException {
throw const MovieApiException('Error al consultar la información');
}
}
Future<List<Movie>> getMoviesRecommendations() async {
try {
String url =
"${_environmentConfig.domainApi}/${_environmentConfig.apiVersion}/tv/top_rated?api_key=${_environmentConfig.movieApiKey}&language=en-US&page=1";
final response =
await _http.request(typeHttp: EnumHttpType.get, urlApi: url);
if (response.statusCode != 200) {
throw const MovieApiException(
'Error al consulta las series recomendadas');
}
List<Movie> movies = allMoviesFromJson(response.body);
return movies;
} on ClientException {
throw const MovieApiException('Error al consultar los recomendados');
}
}
Future<MovieExtend> getDetailMovie(int id) async {
try {
String url =
"${_environmentConfig.domainApi}/${_environmentConfig.apiVersion}/tv/$id?api_key=${_environmentConfig.movieApiKey}&language=en-US&page=1";
final Response response =
await _http.request(typeHttp: EnumHttpType.get, urlApi: url);
if (response.statusCode != 200) {
throw const MovieApiException(
'Error al consulta el detalle de la serie');
}
MovieExtend movieExtend = movieExtendFromJson(response.body);
return movieExtend;
} on ClientException {
throw const MovieApiException(
'Error al consultar el detalle de la serie');
}
}
Future<List<Movie>> getAirtodayMovies() async {
try {
String url =
"${_environmentConfig.domainApi}/${_environmentConfig.apiVersion}/tv/airing_today?api_key=${_environmentConfig.movieApiKey}&language=en-US&page=1";
final Response response =
await _http.request(typeHttp: EnumHttpType.get, urlApi: url);
if (response.statusCode != 200) {
throw const MovieApiException(
'Error al consultar las series, intente nuevamente mas tarde');
}
List<Movie> movies = allMoviesFromJson(response.body);
return movies;
} on ClientException {
throw const MovieApiException('Error al consultar las series de hoy');
}
}
}
htt_request.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:watch_movie_app/src/domain/enums/enums.dart';
/// Clase que nos permite hacer peticiones Http
/// usando la libreria http.dar
class HttpRequest {
final http.Client _httpClient;
late String? token;
HttpRequest({http.Client? httpClient})
: _httpClient = httpClient ?? http.Client();
Future<http.Response> request(
{required EnumHttpType typeHttp, required String urlApi, data}) async {
Map<String, String> headers = {'Content-Type': 'application/json'};
Uri url = Uri.parse(urlApi);
switch (typeHttp) {
case EnumHttpType.post:
return _httpClient.post(url, body: data, headers: headers);
case EnumHttpType.get:
return _httpClient.get(url, headers: headers);
case EnumHttpType.patch:
return _httpClient.patch(url, headers: headers);
case EnumHttpType.put:
return _httpClient.put(url, headers: headers);
case EnumHttpType.delete:
return _httpClient.delete(url, headers: headers);
default:
return _httpClient.get(url);
}
}
}
final httpClientProvider = Provider<HttpRequest>((ref) => HttpRequest());
error detail
MissingStubError: 'get'
No stub was found which matches the arguments of this method call:
get(https://api.themoviedb.org/3/tv/popular?api_key=4dc138c853e44e4ea1d3dfd746fe451d&language=en-US&page=1, {headers: {Content-Type: application/json}}\)
Add a stub for this method using Mockito's 'when' API, or generate the MockClient mock with a MockSpec with 'returnNullOnMissingStub: true' (see https://pub.dev/documentation/mockito/latest/annotations/MockSpec-class.html\).
package:mockito/src/mock.dart 191:7 Mock._noSuchMethod
package:mockito/src/mock.dart 185:45 Mock.noSuchMethod
test\movies_provider_test.mocks.dart 45:14 MockClient.get
package:watch_movie_app/src/data/data_source/remote/http_request.dart 23:28 HttpRequest.request
package:watch_movie_app/src/domain/services/movie_service.dart 26:23 MovieService.getMovies
test\movies_provider_test.dart 36:36 main.<fn>
test\movies_provider_test.dart 17:68
link doc: mockito unit testing
CodePudding user response:
Manually mocking http.Client
is tricky. Stubs must match arguments exactly. In your case, you created a stub for:
when(mockHttp.get(url)).thenAnswer(...);
but the error indicates what was actually called:
No stub was found which matches the arguments of this method call:
get(<Long URL omitted>, {headers: {Content-Type: application/json}}\)
Your stub is not registered for a call that supplies a headers
argument.
You really should avoid trying to create a manual mock for http.Client
and instead use the MockClient
class explicitly provided by package:http
. It's much easier to use.
CodePudding user response:
Indeed, as my colleague @jamesdlin commented, the solution was to use the MockClient class, below I share the implementation working correctly in case someone goes through this, thank you very much jamesdlin
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:watch_movie_app/src/data/data_source/remote/http_request.dart';
import 'package:watch_movie_app/src/data/models/models.dart';
import 'package:watch_movie_app/src/domain/services/movie_service.dart';
import 'mocks/popular_movies.dart';
void main() {
test('returns an instance of movies if the http call completed sucessfully',
() async {
final mockHttp = MockClient((_) async => http.Response(fakeMovies, 200));
final container = ProviderContainer(
overrides: [
httpClientProvider.overrideWithValue(HttpRequest(httpClient: mockHttp)),
],
);
addTearDown(container.dispose);
final movieService = container.read(movieServiceProvider);
expectLater(await movieService.getMovies(), isInstanceOf<List<Movie>>());
});
}