Flutter: How to unit testing moor/drift? - flutter

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

Related

Expected: <false> Actual: <Closure: () => Future<bool>> in flutter unit testing

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

Flutter testing : when testing server failure returns null

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

how to mock the state of a StateNotifierProvider flutter

my test is throwing an exception because there is a StateNotifierProvider inside which is not overridden. for a regular Provider, i can override it using providerContainer, but for the state of a stateNotifierProvider, I don't know how to do it. I tried my best but I reached the limit of my best. I already saw this and this but it didn't help.
Appreciate much if someone could help me out of this. Thanks
My service File
class ReportService {
final Ref ref;
ReportService({
required this.ref,
});
Future<void> testReport() async {
//* How can i override this provider ?
final connection = ref.read(connectivityServiceProvider);
if (connection) {
try {
await ref.read(reportRepositoryProvider).testFunction();
} on FirebaseException catch (e, st) {
ref.read(errorLoggerProvider).logError(e, st);
throw Exception(e.message);
}
} else {
throw Exception('Check internet connection...');
}
}
}
final reportServiceProvider = Provider<ReportService>((ref) => ReportService(
ref: ref,
));
My test file
void main() {
WidgetsFlutterBinding.ensureInitialized();
final reportRepository = MockReportRepository();
ReportService makeReportService() {
final container = ProviderContainer(overrides: [
reportRepositoryProvider.overrideWithValue(reportRepository),
]);
addTearDown(container.dispose);
return container.read(reportServiceProvider);
}
test('test test', () async {
//How to stub the connectivityServiceProvider here ?
when(reportRepository.testFunction)
.thenAnswer((invocation) => Future.value());
final service = makeReportService();
await service.testReport();
verify(reportRepository.testFunction).called(1);
});
My StateNotifierProvider
class ConnectivityService extends StateNotifier<bool> {
ConnectivityService() : super(false);
}
final connectivityServiceProvider =
StateNotifierProvider<ConnectivityService, bool>(
(ref) => ConnectivityService());

type 'Null' is not a subtype of type 'Future<Response>' when testing mocked http client with Mocktail

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,
),
);

How to mock a future method and not get type 'Null' is not a subtype of type 'Future<>' in flutter

I want to mock this class and this specific method
class FeedApiService extends ApiService {
Future<FeedResponse> fetchFeed(Map<String, String> params) async {
...
}
...
}
My unit test is like this
class FeedApiServiceMock extends Mock implements FeedApiService {}
void main() {
test('..', () {
FeedApiServiceMock feedApiServiceMock = FeedApiServiceMock();
when(feedApiServiceMock.fetchFeed({})).thenAnswer(
(_) => Future.value(FeedResponse(items: 1)),
);
expect(await feedApiServiceMock.fetchFeed({}).items, 1);
});
}
I just want to see that fetch feed is mocked correctly, but I'm getting this error:
type 'Null' is not a subtype of type 'Future<FeedResponse>'
See if adding async in both the test and thenAnswer method solves the problem.
void main() {
test('..', () async{
FeedApiServiceMock feedApiServiceMock = FeedApiServiceMock();
when(feedApiServiceMock.fetchFeed({})).thenAnswer(
(_) async=> Future.value(FeedResponse(items: 1)),
);
expect(await feedApiServiceMock.fetchFeed({}).items, 1);
});
}