I'm writing unit tests to test the authorise function below which is in AuthService class.
Future<bool> authorize() async {
AuthorizationTokenResponse? authResult;
try {
authResult = await appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
bsEnv.AD_CLIENT_ID!,
));
if (authResult != null) {
return true;
}
} on PlatformException catch (e) {
forgetPassword();
}
}
return false;
}
I am using Mocktail to make the mocks. Below is one written test for the authorise function mentioned in the above code.
class MockBEnv extends Mock implements BEnv {}
class MockSharedPrefsService extends Mock implements SharedPrefsService {}
class MockFlutterAppAuth extends Mock implements FlutterAppAuth {}
class MockAuthorizationServiceConfiguration extends Mock
implements AuthorizationServiceConfiguration {}
class MockAuthorizationTokenResponse extends Mock
implements AuthorizationTokenResponse {}
class MockAuthorizationTokenRequest extends Mock
implements AuthorizationTokenRequest {}
class MockTokenResponse extends Mock implements TokenResponse{}
class MockTokenRequest extends Mock implements TokenRequest{
}
void main() {
late AuthService authService;
late MockFlutterAppAuth mockFlutterAppAuth;
late MockBEnv mockBEnv;
late MockSharedPrefsService mockStorageService;
late MockAuthorizationTokenResponse mockAuthorizationTokenResponse;
late MockAuthorizationTokenRequest mockAuthorizationTokenRequest;
const tUrl = 'https://endpoint/';
const tUserImp = 'user';
const tAppLan = 'sv-SE';
const tClientId = 'client';
const tIosUri = 'ios';
const tTokenEndPoint = 'abcdeff';
setUp(() {
mockFlutterAppAuth = MockFlutterAppAuth();
mockBEnv = MockBEnv();
mockStorageService = MockSharedPrefsService();
mockAuthorizationTokenRequest = MockAuthorizationTokenRequest();
mockAuthorizationTokenResponse = MockAuthorizationTokenResponse();
authService = AuthService(
appAuth: mockFlutterAppAuth,
bsEnv: mockBEnv,
sharedPrefs: mockStorageService);
});
group('authorize', () {
test(
'should return true when the call to authorized method if result is not null',
() async {
// Arrange
when(() => mockBEnv.AD_CLIENT_ID).thenAnswer((_) => tClientId);
when(() => mockFlutterAppAuth
.authorizeAndExchangeCode(mockAuthorizationTokenRequest))
.thenAnswer((_)=> Future.value(mockAuthorizationTokenResponse));
// Act
final result = authService.authorize;
// Assert
expect(() => result(), equals(false));
});
});
}
My test fails and below is the error message I get,
Expected: < false >
Actual: <Closure: () => Future>
How can I fix this issue?
authService.authorize is an asynchronous method, returning a Future<bool>.
So your testcase should be
final result = await authService.authorize();
expect(result, equals(false));
The await keyword leads to an async execution that resolves the Future into an actual value
Related
I'm using flutter test to do unit testing. The package use to mocking is mocktail package (https://pub.dev/packages/mocktail). When I run the test which is to test the server failure scenario, the actual result I get from this is null.
But the expected result is <Instance of 'Failure'>.
How can I fix this issue?
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockHttpService extends Mock implements HttpService {}
class MockAthleteEnv extends Mock implements AthleteEnv {}
class MockSharedPrefsService extends Mock implements SharedPrefsService {}
class MockAthleteContactDetailsReq extends Mock implements AthleteContactDetailsReq{}
void main() {
late ContactDetailsService contactDetailsService;
late MockHttpService mockHttpService;
late MockAthleteEnv mockAthleteEnv;
late MockSharedPrefsService mockStorageService;
late MockAthleteContactDetailsReq mockAthleteContactDetailsReq;
setUp(() {
mockHttpService = MockHttpService();
mockAthleteEnv = MockAthleteEnv();
mockStorageService = MockSharedPrefsService();
mockAthleteContactDetailsReq = MockAthleteContactDetailsReq();
contactDetailsService = ContactDetailsService(
httpService: mockHttpService,
athleteEnv: mockAthleteEnv,
storageService: mockStorageService);
});
group('add athlete contact details', () {
const tAthleteId = 001;
const tAthleteEndpoint = 'https://endpoint/';
const tData = {
'firstName':'John',
'lastName':'Doe',
'contactNumber': '0932445178',
'areaCode':'94',
'email':'johndoe3#mailinator.com',
};
test(
'should return server failure when the call to remote data source is unsuccessful',
() async {
// Arrange
when(() => mockStorageService.getFromDisk(any()))
.thenAnswer((_) => tAthleteId);
when(() => mockAthleteEnv.API_ATHLETE_ENDPOINT)
.thenAnswer((_) => tAthleteEndpoint);
when(() => mockAthleteContactDetailsReq.toJson()).thenAnswer((_) => tData );
when(() => mockHttpService.post(any(), any())).thenThrow(
DioError(
response: Response(
statusCode: 400,
requestOptions: RequestOptions(path: ""),
),
requestOptions: RequestOptions(path: ""),
)
);
// Act
final result = await contactDetailsService.addAthleteContactDetails(mockAthleteContactDetailsReq);
// Assert
expect(result, throwsA(const TypeMatcher<Failure>()));
});
});
}
I tried to run unit test but i found an issue like below:
type 'Null' is not a subtype of type 'LoginResponse' in type cast
dart:async new Future.value
test/mock_utils.mocks.dart 259:22 MockBaseRepository.call
package:swap_recycling/features/auth/data/repository/login/login_repository_impl.dart 33:42 LoginRepositoryImpl.login
test/features/auth/data/repository/login/login_repository_impl_test.dart 30:38
and these my codes:
test.dart
void main() {
late MockLoginApiService _mockLoginApiService;
late MockSecureStorage _mockSecureStorage;
late MockBaseRepository _mockBaseRepository;
setUp(() {
_mockLoginApiService = MockLoginApiService();
_mockSecureStorage = MockSecureStorage();
_mockBaseRepository = MockBaseRepository();
});
test('Given LoginRepository When login Then return loginResponseSuccess',
() async {
final _repository = LoginRepositoryImpl(
_mockLoginApiService,
_mockSecureStorage,
_mockBaseRepository,
);
final result = await _repository.login(loginRequest);
expect(result, loginResponseSuccess);
verify(_mockLoginApiService.login(loginRequest)).called(1);
});
Login_repository.dart
final loginRepositoryProvider = Provider<LoginRepository>((ref) {
final loginApiService = ref.watch(provideLoginApiService);
final secureStorage = ref.watch(secureStorageProvider);
final baseRepository = ref.watch(baseRepositoryProvider);
return LoginRepositoryImpl(loginApiService, secureStorage, baseRepository);
});
class LoginRepositoryImpl extends LoginRepository {
final LoginApiService _loginApiService;
final SecureStorage _secureStorage;
final BaseRepository _baseRepository;
LoginRepositoryImpl(
this._loginApiService,
this._secureStorage,
this._baseRepository,
);
#override
Future<LoginResponse> login(LoginRequest request) async {
final result = await _baseRepository.call<LoginResponse>(
() => _loginApiService.login(request),
);
return result;
}
base_repository.dart
final baseRepositoryProvider = Provider<BaseRepository>((ref) {
return BaseRepository();
});
class BaseRepository {
Future<T> call<T>(FutureOr<T> Function() call) async {
try {
return await call();
} on DioError catch (e) {
if (e.error is SocketException) {
throw Failure(message: e.message);
}
if (e.response?.statusCode == serverErrorCode) {
throw Failure(
message: 'Server has some issue, please try again',
code: e.response?.statusCode,
);
}
throw Failure(
message: e.response?.statusMessage ?? 'Something went wrong',
code: e.response?.statusCode,
exception: e,
);
}
}
}
I tried to create a reuse function to return HTTP calls or handle errors.
I set generic type and send LoginResponse to tell BaseRepository that I want to return LoginResponse.
I can run it on an emulator but it doesn't work in a unit test.
Why It returns Null type?
I used mockito to generate mock class
mocks.dart
#GenerateNiceMocks([
//Repository
MockSpec<LoginRepository>(),
MockSpec<SettingRepository>(),
//Service
MockSpec<LoginApiService>(),
MockSpec<SecureStorage>(),
//Base
MockSpec<BaseRepository>(),
MockSpec<LoginRepositoryImpl>(),
])
I already implementation of Drift for local storage, and want make it testable function. But I get stack and idk how to fix it the unit test.
HomeDao
#DriftAccessor(tables: [RepositoriesTable])
class HomeDao extends DatabaseAccessor<AppDatabase> with _$HomeDaoMixin {
HomeDao(AppDatabase db) : super(db);
Future<List<RepositoriesTableData>> getRepositories() async =>
await select(repositoriesTable).get();
}
AppDatabase
#DriftDatabase(
tables: [RepositoriesTable],
daos: [HomeDao],
)
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
#override
int get schemaVersion => 1;
}
QueryExecutor _openConnection() {
return SqfliteQueryExecutor.inDatabaseFolder(
path: 'db.sqlite',
logStatements: true,
);
}
LocalDataSources
abstract class GTHomeLocalDataSource {
const GTHomeLocalDataSource();
Future<List<RepositoriesTableData>> getRepositories();
}
class GTHomeLocalDataSourceImpl implements GTHomeLocalDataSource {
final AppDatabase appDatabase;
const GTHomeLocalDataSourceImpl({required this.appDatabase});
#override
Future<List<RepositoriesTableData>> getRepositories() async =>
await appDatabase.homeDao.getRepositories();
}
UnitTesting
void main() => testGTHomeLocalDataSource();
class MockDatabaseHandler extends Mock implements AppDatabase {}
void testGTHomeLocalDataSource() {
late GTHomeLocalDataSource localDataSource;
late AppDatabase databaseHandler;
setUp(() {
databaseHandler = MockDatabaseHandler();
localDataSource = GTHomeLocalDataSourceImpl(
appDatabase: databaseHandler,
);
});
group("GTHomeLocalDataSource -", () {
test(''' \t
GIVEN Nothing
WHEN call getRepositories
THEN databaseHandler select function has been called and return list of RepositoriesTableData
''', () async {
// GIVEN
when(() => databaseHandler.homeDao.getRepositories())
.thenAnswer((_) => Future.value(repositoriesDummyTable));
// WHEN
final result = await localDataSource.getRepositories();
// THEN
verify(() => databaseHandler.homeDao.getRepositories());
expect(result, isA<List<RepositoriesTableData>>());
expect(result.length, repositoriesDummyTable.length);
expect(result.first.language, repositoriesDummyTable.first.language);
});
});
tearDown(() async {
await databaseHandler.close();
});
}
My function is work well for get data from the local db and show it in the app, but when running as unit test, I stacked with this error.
package:gt_core/local/database/database_module.g.dart 424:22 MockDatabaseHandler.homeDao
package:gt_home/data/data_sources/gt_home_local_datasource.dart 20:25 GTHomeLocalDataSourceImpl.getRepositories
test/data/data_sources/gt_home_local_datasource_test.dart 35:44 testGTHomeLocalDataSource.<fn>.<fn>
test/data/data_sources/gt_home_local_datasource_test.dart 29:12 testGTHomeLocalDataSource.<fn>.<fn>
type 'Null' is not a subtype of type 'Future<void>'
package:drift/src/runtime/api/db_base.dart 125:16 MockDatabaseHandler.close
test/data/data_sources/gt_home_local_datasource_test.dart 47:27 testGTHomeLocalDataSource.<fn>
test/data/data_sources/gt_home_local_datasource_test.dart 46:12 testGTHomeLocalDataSource.<fn>
===== asynchronous gap ===========================
dart:async _completeOnAsyncError
test/data/data_sources/gt_home_local_datasource_test.dart testGTHomeLocalDataSource.<fn>
test/data/data_sources/gt_home_local_datasource_test.dart 46:12 testGTHomeLocalDataSource.<fn>
type 'Future<List<RepositoriesTableData>>' is not a subtype of type 'HomeDao'
Anyone know how to fix it?
If you use Mocking to test Drift database, then you'll need to mock the method call as well, otherwiser the method will return null which is the default behavior for Mockito. For example.
// return rowid
when(db.insertItem(any)).thenAnswer((_) => 1);
However it is recommended as per Drift documentation to use in-memory sqlite database which doesn't require real device or simulator for testing.
This issue was also has been discussed here
Using in memory database
import 'package:drift/native.dart';
import 'package:test/test.dart';
import 'package:my_app/src/database.dart';
void main() {
MyDatabase database;
MyRepo repo;
setUp(() {
database = MyDatabase(NativeDatabase.memory());
repo = MyRepo(appDatabase: database);
});
tearDown(() async {
await database.close();
});
group('mytest', () {
test('test create', () async {
await repo.create(MyDateCompanion(title: 'some name'));
final list = await repo.getItemList();
expect(list, isA<MyDataObject>())
})
});
}
I have written a test for a simple HTTP get using Mocktail to mock the HTTP client. When I call the get method in the test I receive "type 'Null' is not a subtype of type 'Future'".
Anyone any idea why this might be?
Here is the test:
class MockHttpClient extends Mock implements http.Client {}
void main() {
late IdRemoteDataSourceImpl dataSource;
late MockHttpClient mockHttpClient;
setUp(
() {
mockHttpClient = MockHttpClient();
dataSource = IdRemoteDataSourceImpl(client: mockHttpClient);
},
);
group('Get id', () {
test(
'when response code is 200',
() async {
final url = Uri.parse('https://api.test.com/');
const tUsername = 'username';
final accountJson = json.decode(
fixture('account.json'),
// ignore: avoid_as
) as Map<String, dynamic>;
final tIdModel = IdModel.fromJson(accountJson);
// arrange
when(() => mockHttpClient.get(url))
.thenAnswer((_) async => http.Response(
fixture('account.json'),
200,
));
// act
final testResult = await dataSource.getId(tUsername);
// assert
// expect(testResult, tIdModel);
},
);
});
}
The error occurs when the following line runs:
final testResult = await dataSource.getId(tUsername);
Code being tested:
import 'dart:convert';
import 'package:http/http.dart' as http;
class IdModel {
IdModel({required this.id});
final String id;
factory IdModel.fromJson(Map<String, dynamic> json) {
return IdModel(id: json['id'].toString());
}
}
abstract class IdRemoteDataSource {
Future<IdModel> getId(String username);
}
class IdRemoteDataSourceImpl implements IdRemoteDataSource {
IdRemoteDataSourceImpl({required this.client});
final http.Client client;
#override
Future<IdModel> getId(String username) async {
final url = Uri.parse('https://api.test.com/query?username=$username');
final response = await client.get(url);
// ignore: avoid_as
final responseJson = json.decode(response.body) as Map<String, dynamic>;
return IdModel.fromJson(responseJson);
}
}
Error type 'Null' is not a subtype of type 'Future'... occurs when you call method that has not been implemented for mock object or there are different parameters passed to it.
In your code you passed different url parameter to get(...) method. Http client mock waiting for 'https://api.test.com/' but actually 'https://api.test.com/query?username=$username' has been passed.
You have two options to solve it.
Pass the same url to mocked method from when(...) that will be passed during test:
const tUsername = 'username';
final url = Uri.parse('https://api.test.com/query?username=$tUsername');
...
// arrange
when(() => mockHttpClient.get(url))
.thenAnswer((_) async => http.Response(
fixture('account.json'),
200,
),
);
Use any matcher (if you don't care which parameter passed):
registerFallbackValue(Uri.parse(''));
...
when(() => mockHttpClient.get(any()))
.thenAnswer((_) async => http.Response(
fixture('account.json'),
200,
),
);
I am trying to test my catch in my signInAnonyymous() function. Yet, I am constantly getting the error stated in the title.
Here is my Mocked Auth class (edited for brevity):
class MockFirebaseAuth extends Mock implements FirebaseAuth {
final bool signedIn;
final bool isEmployee;
MockFirebaseAuth({this.isEmployee = false, this.signedIn = false});
#override
Future<UserCredential> signInAnonymously() async {
return MockUserCredential(isAnonymous: true);
}
}
And, this is my test:
group('Sign-In-Anonymously', () {
setUp(() {
mockFunctions = MockFirebaseFunctions();
mockAuth = MockFirebaseAuth();
mockCrashltyics = MockCrashlytics();
mockFirestore = MockFirebaseFirestore();
authRemoteService = AuthServiceFirebase(
crashlytics: mockCrashltyics,
firestoreService: mockFirestore,
functions: mockFunctions,
service: mockAuth,
);
authProvider = AuthProvider(authRemoteService);
});
test(
'Should return a null AuthUser and non-null errorMessage on exception thrown',
() async {
when(mockAuth.signInAnonymously())
.thenThrow(MockFirebaseAuthException('message', 'code'));
//act
await authProvider.signInAnonymously();
//assert
expect(authProvider.authUser, isNull);
expect(authProvider.errorMessage, isNotNull);
});
});
I have researched and tried so many different ways and still can't test my catch. Any help would be appreciated. Thank you in advance.
If you're going to use 'when', you can't override the method in the mock. You need to provide an answer from the "thenAnswer".