Mocktail error: No method stub was called from within `when()` - dart

I'm using mocktail 0.3.0 package to test the request() method from HttpAdapter class. This method should call post() method of the Client class of the http package.
class HttpAdapter {
static const headers = {
HttpHeaders.contentTypeHeader: 'application/json',
HttpHeaders.acceptHeader: 'application/json',
};
final Client client;
HttpAdapter(this.client);
Future<void> request({
required String url,
required String method,
HttpClientBody? body,
}) async {
await client.post(Uri.parse(url), headers: HttpAdapter.headers);
}
}
class ClientSpy extends Mock implements Client {
void mockPost(String url) {
when(() => post(
Uri.parse(url),
headers: any(named: 'headers'),
)).thenAnswer(
(_) async => Response('{}', HttpStatus.ok),
);
}
}
When I test using the code below, everything goes well:
void main() {
test('Should call post() with correct parameters', () async {
// Arrange
final client = ClientSpy();
final sut = HttpAdapter(client);
when(() => client.post(
Uri.parse(url),
headers: HttpAdapter.headers,
)).thenAnswer(
(_) async => Response('{}', HttpStatus.ok),
);
// Act
await sut.request(url: url, method: method);
// Assert
verify(() => client.post(
Uri.parse(url),
headers: HttpAdapter.headers,
));
});
}
But if I replace the when() instruction by mockPost() method I get the error: Bad state: No method stub was called from within 'when()'. Was a real method called, or perhaps an extension method?
void main() {
test('Should call post() with correct parameters', () async {
// Arrange
final client = ClientSpy();
final sut = HttpAdapter(client);
client.mockPost();
// Act
await sut.request(url: url, method: method);
// Assert
verify(() => client.post(
Uri.parse(url),
headers: HttpAdapter.headers,
));
});
}
What am I doing wrong?

I think I figured out the problem.
The ClientSpy class implements the Client class from http package.
The Client class has a method called post(), witch is the one I want to call.
However, http package has a post() function also (I did't know that). So, I was calling the post() function instead Client.post() method.
The code below is working fine now:
class ClientSpy extends Mock implements Client {
void mockPost(String url) {
when(() => this.post(
Uri.parse(url),
headers: any(named: 'headers'),
)).thenAnswer(
(_) async => Response('{}', HttpStatus.ok),
);
}
}
or
import 'package:http/http.dart' as http;
class ClientSpy extends Mock implements http.Client {
void mockPost(String url) {
when(() => post(
Uri.parse(url),
headers: any(named: 'headers'),
)).thenAnswer(
(_) async => Response('{}', HttpStatus.ok),
);
}
}

Related

"TypeError: OAuth2Strategy requires a verify callback." but i send it (nestjs)

I call the constructor of OAuth2Strategy in app.controller.ts, i send it the 2 params it needs: options with clientID, callbackURL etc, and a verify callback function. But i have this error, looks like i don't send a verify callback function to the constructor, but i did. The error happens in node_modules/passport-oauth2/lib/strategy.js.
app.controller.ts:
import { Body, Controller, Get, Post, Query, Res, Req, Next, UnauthorizedException, UseGuards } from '#nestjs/common';
import { PartieService, JoueurService, CanalService } from './app.service';
import { Joueur, Partie, Canal } from './app.entity';
import { AuthGuard } from '#nestjs/passport';
import passport = require("passport");
import OAuth2Strategy = require("passport-oauth2");
//var OAuth2Strategy = require('passport-oauth2').OAuth2Strategy;
//import OAuth2Strategy from 'passport-oauth2';
#Controller()
export class AppController {
constructor(private readonly partieService: PartieService, private readonly joueurService: JoueurService,
private readonly canalService: CanalService) {}
#Get('/oauth2')
async oauth2(#Req() req, #Res() res, #Next() next) {
passport.use('oauth2', new OAuth2Strategy(
{
clientID: 'my_clientID',
clientSecret: 'my_clientSecret',
authorizationURL: 'https://api.abc.com/oauth/authorize',
tokenURL: 'https://api.abc.fr/oauth/token',
callbackURL: 'http://localhost:3000/Connection'
},
async (accessToken: string, refreshToken: string, profile: any, done: Function) => {
//console.log(profile);
try {
if (!accessToken || !refreshToken || !profile) {
return done(new UnauthorizedException(), false);
}
const user: Joueur = await this.joueurService.findOrCreate(profile);
return done(null, user);
} catch (err) {
return done(new UnauthorizedException(), false);
}
} ));
passport.authenticate('oauth2');
return res.sendFile("/home/user42/Documents/Projets/ft_transcendence/services/pong/pong/public/connection.html");
}
#Get('/Connection')
//#UseGuards(AuthGuard('oauth2'))
async Callback(#Req() req, #Res() res, #Next() next) {
passport.authenticate('oauth2', (err, user, info) => {
if (err || !user) {
req.session.destroy();
return res.status(401).json({
message: 'Unauthorized',
});
}
req.user = user;
return next();
})(req, res, next);
const utilisateur: Joueur = req.user;
console.log(utilisateur);
}
[...]
}
node_modules/passport-oauth2/lib/strategy.js:
function OAuth2Strategy(options, verify) {
if (typeof options == 'function') {
verify = options;
options = undefined;
}
options = options || {};
if (!verify) { throw new TypeError('OAuth2Strategy requires a verify callback'); }
[...]
}
Error:
[Nest] 6550 - 05/02/2023, 18:34:54 ERROR [ExceptionHandler] OAuth2Strategy requires a verify callback
TypeError: OAuth2Strategy requires a verify callback
at new OAuth2Strategy (/home/user42/Documents/Projets/ft_transcendence/services/pong/pong/node_modules/passport-oauth2/lib/strategy.js:84:24)
at Injector.instantiateClass (/home/user42/Documents/Projets/ft_transcendence/services/pong/pong/node_modules/#nestjs/core/injector/injector.js:340:19)
at callback (/home/user42/Documents/Projets/ft_transcendence/services/pong/pong/node_modules/#nestjs/core/injector/injector.js:53:45)
at Injector.resolveConstructorParams (/home/user42/Documents/Projets/ft_transcendence/services/pong/pong/node_modules/#nestjs/core/injector/injector.js:132:24)
at Injector.loadInstance (/home/user42/Documents/Projets/ft_transcendence/services/pong/pong/node_modules/#nestjs/core/injector/injector.js:57:13)
at Injector.loadProvider (/home/user42/Documents/Projets/ft_transcendence/services/pong/pong/node_modules/#nestjs/core/injector/injector.js:84:9)
at async Promise.all (index 3)
at InstanceLoader.createInstancesOfProviders (/home/user42/Documents/Projets/ft_transcendence/services/pong/pong/node_modules/#nestjs/core/injector/instance-loader.js:47:9)
at /home/user42/Documents/Projets/ft_transcendence/services/pong/pong/node_modules/#nestjs/core/injector/instance-loader.js:32:13
at async Promise.all (index 1)
`
I tried to change the import OAuth2Strategy many times (like the commented lines). I tried a lot of things but i cannot remember all. I found no answer in internet, and no more with the not-so-well-chatgpt.
I am new in nestjs and this error is weird for me, how can it ask me a parameter that i send ? Can someone help me to resolve this pls ? :)
Sorry if the answer is obvious :/ , it's my first API with nestjs
The error was coming from another file, my bad, it's fixed.

Dio unit test error, type 'Null' is not a subtype of type 'Interceptors'

Currently I am working on my flutter app using Dio package as my networking and I want to create test on the dio post (wrapped within appclient). Here my code for
app_client.dart
abstract class AppClient{
Future<Map<String, dynamic>> get(String path, {Map<String, String>? queryParameters, Map<String, String>? headers});
Future<Map<String, dynamic>> post(String path, dynamic data, {Map<String, String>? queryParameters, Map<String, String>? headers});
}
class AppClientImpl implements AppClient{
late Dio dio;
late Interceptors interceptors;
AppClientImpl({required this.dio}){
//get language code
String languageCode = StorageUtil.getSavedLanguage();
if(languageCode == "id-ID"){
languageCode = "id_ID";
}else{
languageCode = "en_US";
}
BaseOptions baseOptions = BaseOptions(
baseUrl: ServiceUrl.baseUrl,
headers: {
"Accept" : "application/json",
"Content-Type" : "application/json",
},
queryParameters: {
"language" : languageCode,
"channel" : "mobile"
}
);
dio.options = baseOptions;
interceptors = Interceptors();
interceptors.add(LogInterceptor(request: true, requestBody: true, requestHeader: true, responseBody: true, responseHeader: true));
//COMMENT BECAUSE FAILED WHEN TESTING
dio.interceptors.addAll(interceptors); // ERROR HERE!!!!
}
#override
Future<Map<String, dynamic>> get(String path, {Map<String, String>? queryParameters, Map<String, String>? headers}) async {
// return await dio.get(path, queryParameters: queryParameters);
throw UnimplementedError();
}
Future<Map<String, dynamic>> post(String path, dynamic data, {Map<String, String>? queryParameters, Map<String, String>? headers}) async{
Response response = await dio.post(path, queryParameters: queryParameters, data: Map<String, dynamic>.from(data));
if(response.statusCode == 200){
return jsonDecode(response.data);
}else{
throw Exception();
}
}
}
and here is my test class
class MockDio extends Mock implements Dio{}
void main(){
late MockDio mockDio;
late AppClientImpl appClient;
setUp((){
mockDio = MockDio();
appClient = AppClientImpl(dio: mockDio);
});
final tResponse = jsonDecode(fixture("token/token_success.json"));
final tData = {};
group("post method", (){
test(
"should return data when status code is 200",
()async{
when(
() => mockDio.post(any(), queryParameters: any(named: "queryParameters"), data: any(named: "data"))
).thenAnswer(
(invocation) async => Response(requestOptions: RequestOptions(path: "/sample"), data: fixture("token/token_success.json"), statusCode: 200)
);
final result = await appClient.post("/sample", tData);
verify(() => mockDio.post(any(), queryParameters: any(named: "queryParameters"), data: any(named: "data"))).called(1);
expect(result, tResponse);
}
);
});
}
As you can see, I inject dio instance to my appclient class and add global configuration there including interceptors.
I think everything is ok until I get these error.
Testing started at 08.54 ...
package:dio/src/dio.dart 46:20 MockDio.interceptors
package:eazyconnect/data/network/app_client/app_client.dart 44:9 new AppClientImpl
test/data/network/app_client/app_client_test.dart 23:17 main.<fn>
type 'Null' is not a subtype of type 'Interceptors'
Why this is happen? Any help and suggestion would be great!
Thanks!
You need mock interceptors too. You need pass interceptors like parameters, for class http, you can see the list interceptors have setted on Dio instance dont no in DioMock, that's why the error are happening

How to use dart shelf_router in web only dart project

From the example given at this place :
https://pub.dev/packages/shelf_router/example
I have written the router part like this :
class Service {
Handler get handler {
final router = Router();
router.get('/say-hi/<name>', (Request request, String name) {
return Response.ok('hi $name');
});
router.get('/user/<userId|[0-9]+>', (Request request, String userId) {
return Response.ok('User has the user-number: $userId');
});
router.get('/wave', (Request request) async {
await Future.delayed(Duration(milliseconds: 100));
return Response.ok('_o/');
});
router.mount('/api/', Api().router);
router.all('/<ignored|.*>', (Request request) {
return Response.notFound('Page not found');
});
return router;
}
}
class Api {
Future<Response> _messages(Request request) async {
return Response.ok('[]');
}
Router get router {
final router = Router();
router.get('/messages', _messages);
router.get('/messages/', _messages);
uter.all('/<ignored|.*>', (Request request) => Response.notFound('null'));
return router;
}
}
and from the main method it tells to use it like this:
void main() async {
final service = Service();
final server = await shelf_io.serve(service.handler, 'localhost', 8080);
print('Server running on localhost:${server.port}');
}
but in web-only project we don't do : final server = await shelf_io.serve(service.handler, 'localhost', 8080);
I was thinking of creating single page application using a shelf router, I saw from the documentation it says that it is compatible with the dart web js platform
My expectation was :
if I write this in web :
router.get('/say-hi/<name>', (Request request, String name) {
return Response.ok('hi $name');
});
then when I will hit /say-hi/ram, then it should have returned "hi ram" in the browser

Flutter/Mockito. Testing API provider with mockito, problem with api headers

I want to test my provider with mockito plugin but there is a problem with, as i understand with headers.
shows null when i do print(response)
if i remove headers from API
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Authorization': 'Bearer $token',
},
the test works fine but with headers:
NoSuchMethodError: The getter 'statusCode' was called on null.
Receiver: null
Tried calling: statusCode
dart:core Object.noSuchMethod
package:mba/resources/providers/post_provider.dart 38:20 PostProvider.fetchPosts
===== asynchronous gap ===========================
dart:async _AsyncAwaitCompleter.completeError
package:mba/resources/providers/post_provider.dart PostProvider.fetchPosts
===== asynchronous gap ===========================
dart:async _asyncThenWrapperHelper
package:mba/resources/providers/post_provider.dart PostProvider.fetchPosts
test/resources/providers/post_provider_test.dart 46:39 main.<fn>.<fn>
provider:
import 'package:http/http.dart' as http;
import 'package:mba/env.dart';
import 'package:mba/core/models/post_model.dart';
class PostProvider {
final http.Client client;
final String _api = API;
// Map<String, List<Post>> _postsList = Map();
PostProvider(this.client);
Future<Map<String, List<Post>>> fetchPosts(String token) async {
final response = await client.get(
'${_api}posts',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Authorization': 'Bearer $token',
},
);
print('-------------');
print(response);
print(response.statusCode);
print(response.body);
//todo: decode and mapping
if (response.statusCode == 200) {
return Map();
} else {
throw 'xxxx';
}
}
test file:
import 'dart:io';
import 'package:mba/resources/providers/post_provider.dart';
import 'package:mockito/mockito.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_test/flutter_test.dart';
import 'package:matcher/matcher.dart';
class MockClient extends Mock implements http.Client {}
void main() {
String fixture(String name) =>
File('test/fixtures/posts/$name.json').readAsStringSync();
MockClient mockClient;
PostProvider dataSource;
setUp(() {
mockClient = MockClient();
dataSource = PostProvider(mockClient);
});
group('searchVideos', () {
test(
'returns YoutubeSearchResult when the call completes successfully',
() async {
when(
mockClient.get(
argThat(
startsWith('http://localhost:55005/api'),
),
),
).thenAnswer(
(_) async => http.Response(
fixture('posts'),
200,
headers: {'content-type': 'application/json; charset=utf-8'},
),
);
var result = await dataSource.fetchPosts('resocoder');
print(result);
},
);
});
}
Mockito will respond to your when sentence if and only if all args of the mockClient.get() matches what you have specified.
In you case, you specified an URL, and nothing more. As your code use an URL and headers mockito won't match and won't do your thenAnswer.
In your when sentence, you have to specify the headers argument to match you tested code
when(
mockClient.get(
argThat(
startsWith('http://localhost:55005/api'),
),
headers: anyNamed('headers'), // Add this line
),
).thenAnswer(...);
Here, the anyNamed('headers') will match any header.

How to mock http request in flutter integration test?

I'm trying to do so using Mockito, this is my test:
import 'package:http/http.dart' as http;
import 'package:utgard/globals.dart' as globals;
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
import 'package:mockito/mockito.dart';
class MockClient extends Mock implements http.Client {}
void main() {
group('Login flow', () {
final SerializableFinder loginContinuePasswordButton =
find.byValueKey('login_continue_password_button');
FlutterDriver driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
if (driver != null) {
//await driver.close();
}
});
test('login with correct password', () async {
final client = MockClient();
when(client.post('http://wwww.google.com'))
.thenAnswer((_) async => http.Response('{"title": "Test"}', 200));
globals.httpClient = client;
await driver.enterText('000000');
await driver.tap(loginContinuePasswordButton);
});
});
}
And this is my http request code:
Future<Map<String, dynamic>> post({
RequestType requestType,
Map<String, dynamic> body,
}) async {
final http.Response response =
await globals.httpClient.post('http://wwww.google.com');
print(response);
final Map<String, dynamic> finalResponse = buildResponse(response);
_managerErrors(finalResponse);
return finalResponse;
}
And here I have the global:
library utgard.globals;
import 'package:http/http.dart' as http;
http.Client httpClient = http.Client();
However I continue to receive http errors, that indicates to me that the http wasn't replaced by the mock.
The solution I found was to define the mock in test_driver/app.dart and call the runApp function after that:
import 'package:flutter/widgets.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:utgard/business/config/globals.dart';
import 'package:utgard/main.dart' as app;
class MockClient extends Mock implements http.Client {}
void main() {
enableFlutterDriverExtension();
final MockClient client = MockClient();
// make your mocks here
httpClient = client;
runApp(app.MyApp());
}
Instead of
when(client.post('http://wwww.google.com'))
.thenAnswer((_) async => http.Response('{"title": "Test"}', 200));
try any and then assert it later
when(
mockHttpClient.send(any),
).thenAnswer((_) async => http.Response('{"title": "Test"}', 200));
// ...
final String capt = verify(client.send(captureAny)).captured;
expect(capt, 'http://wwww.google.com');
There's a small chance the call param is not exactly what you mock, so go with any is safer.
We don't see the code you're testing, but it's unlikely that it is going to make this request :
client.post('http://wwww.google.com')
It is anyway a good practice to use mock json files, and you don't want to change the request anytime those mock files change.
I recommend you to use Mocktail instead of Mockito.
That way, you can simulate any call with any :
// Simulate any GET :
mockHttpClient.get(any()))
// Simulate any POST :
mockHttpClient.post(any()))
Here's the complete solution :
class MockClient extends Mock implements http.Client {}
class FakeUri extends Fake implements Uri {}
void main() {
setUp(() {
registerFallbackValue(FakeUri()); // Required by Mocktail
});
tearDown(() {});
MockHttpClient mockHttpClient = MockHttpClient();
group('Login flow', () {
test('login with correct password', () async {
when(() => mockHttpClient.get(any())).thenAnswer(((_) async {
return Response(mockWeatherResponse, 200);
}));
// Call the functions you need to test here
}
}
}

Resources