MissingStubError: 'get' when using mockito 5.1.0 to test api using riverpod
MissingStubError: 'get' when using mockito 5.1.0 to test api using riverpod
我正在尝试使用 mockito 在 http.Client 调用中 return 伪造响应并能够测试该服务。我已经按照文档进行操作。它告诉我应该使用 annotate 来生成一个假的 class,但似乎是 flutter 的 null safe 导致了问题。有谁知道怎么做?修复它谢谢
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());
错误详情
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 文档:
mockito unit testing
手动模拟 http.Client
很棘手。存根必须 完全 匹配参数。在你的例子中,你创建了一个存根:
when(mockHttp.get(url)).thenAnswer(...);
但错误表明实际调用的是什么:
No stub was found which matches the arguments of this method call:
get(<Long URL omitted>, {headers: {Content-Type: application/json}}\)
您的存根未注册提供 headers
参数的调用。
您真的应该避免尝试为 http.Client
创建手动模拟,而是使用 package:http
明确提供的 MockClient
class。使用起来更方便。
确实,正如我的同事@jamesdlin 评论的那样,解决方案是使用 MockClient class,下面我分享了正确运行的实现,以防有人遇到这个问题,非常感谢 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>>());
});
}
我正在尝试使用 mockito 在 http.Client 调用中 return 伪造响应并能够测试该服务。我已经按照文档进行操作。它告诉我应该使用 annotate 来生成一个假的 class,但似乎是 flutter 的 null safe 导致了问题。有谁知道怎么做?修复它谢谢
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());
错误详情
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 文档: mockito unit testing
手动模拟 http.Client
很棘手。存根必须 完全 匹配参数。在你的例子中,你创建了一个存根:
when(mockHttp.get(url)).thenAnswer(...);
但错误表明实际调用的是什么:
No stub was found which matches the arguments of this method call:
get(<Long URL omitted>, {headers: {Content-Type: application/json}}\)
您的存根未注册提供 headers
参数的调用。
您真的应该避免尝试为 http.Client
创建手动模拟,而是使用 package:http
明确提供的 MockClient
class。使用起来更方便。
确实,正如我的同事@jamesdlin 评论的那样,解决方案是使用 MockClient class,下面我分享了正确运行的实现,以防有人遇到这个问题,非常感谢 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>>());
});
}