Home > Net >  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

Time:03-17

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>>());
  });
}
  • Related