Skip to content

Commit

Permalink
feat: unit testing repositories
Browse files Browse the repository at this point in the history
  • Loading branch information
fedecor9 committed Dec 14, 2023
1 parent 16876af commit 78a968f
Show file tree
Hide file tree
Showing 11 changed files with 630 additions and 14 deletions.
3 changes: 2 additions & 1 deletion lib/core/di/di_repository_module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ class RepositoryDiModule {

extension _GetItDiModuleExtensions on GetIt {
void _setupProvidersAndUtils() {
registerLazySingleton(() => HttpServiceDio([AuthInterceptor(get())]));
registerLazySingleton<HttpService>(
() => HttpServiceDio([AuthInterceptor(get())]));
}

void _setupRepositories() {
Expand Down
2 changes: 1 addition & 1 deletion lib/core/source/auth_remote_source.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:flutter_template/core/model/service/service_response.dart';
import 'package:flutter_template/core/source/common/http_service.dart';

class AuthRemoteSource {
final HttpServiceDio _httpService;
final HttpService _httpService;

static const _urlLogin = 'auth/v1/token';

Expand Down
4 changes: 2 additions & 2 deletions lib/core/source/project_remote_source.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'package:flutter_template/core/model/service/service_response.dart';
import 'package:flutter_template/core/model/project.dart';
import 'package:flutter_template/core/model/service/service_response.dart';
import 'package:flutter_template/core/source/common/http_service.dart';

class ProjectRemoteSource {
static const _urlGetProjects = 'rest/v1/projects?select=*';

final HttpServiceDio _httpService;
final HttpService _httpService;

ProjectRemoteSource(this._httpService);

Expand Down
8 changes: 8 additions & 0 deletions pubspec.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 11 additions & 10 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
name: flutter_template
description: A new Flutter project.

publish_to: 'none'
publish_to: "none"

version: 1.0.0+1

environment:
sdk: '>=3.0.0 <4.0.0'
sdk: ">=3.0.0 <4.0.0"
flutter: 3.13.9

dependencies:
Expand All @@ -23,6 +23,7 @@ dependencies:
dio: 5.3.2
equatable: 2.0.5
floor: 1.4.2
mocktail: 1.0.1
flutter_bloc: 8.1.3
flutter_dotenv: 5.1.0
flutter_native_splash: 2.3.2
Expand Down Expand Up @@ -79,19 +80,19 @@ flutter_gen:
flutter_launcher_icons:
android: true
ios: true
image_path: 'icons/ic_launcher.png'
image_path_ios: 'icons/ic_launcher_ios.png' # Transparency not supported on IOS
adaptive_icon_foreground: 'icons/ic_launcher_foreground.png'
adaptive_icon_background: '#ee1a64'
image_path: "icons/ic_launcher.png"
image_path_ios: "icons/ic_launcher_ios.png" # Transparency not supported on IOS
adaptive_icon_foreground: "icons/ic_launcher_foreground.png"
adaptive_icon_background: "#ee1a64"
remove_alpha_ios: true
web:
generate: false
windows:
generate: false

flutter_native_splash:
color: '#ffffff'
image: 'icons/splash_logo.png'
color: "#ffffff"
image: "icons/splash_logo.png"
android_12:
image: 'icons/splash_logo_android_12.png'
branding: 'icons/splash_branding.png'
image: "icons/splash_logo_android_12.png"
branding: "icons/splash_branding.png"
111 changes: 111 additions & 0 deletions test/repositories/project/project_local_source_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import 'dart:math';

import 'package:collection/collection.dart';
import 'package:flutter_template/core/model/db/repository_db_entity.dart';
import 'package:flutter_template/core/source/common/app_database.dart';
import 'package:flutter_template/core/source/project_local_source.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
late AppDatabase database;
late ProjectLocalSource projectDao;

setUp(() async {
database = await $FloorAppDatabase.inMemoryDatabaseBuilder().build();
projectDao = database.projectLocalSource;
});

tearDown(() async {
await database.close();
});

group('Test projects db', () {
test('Get projects from empty db, should return an empty list', () async {
final projects = await projectDao.getProjects().first;
expect(projects, []);
});
test('Insert one project to empty db, should return 1 project', () async {
final project = ProjectDbEntity(
id: 1,
name: 'Test projects',
description: 'Test project description',
url: 'test.com',
imageUrl: '',
language: 'ES',
);
await projectDao.insertProjects([project]);
final projects = await projectDao.getProjects().first;
expect(projects.length, 1);
});
test('get all projects from db, should return 10', () async {
final projects = Iterable.generate(
10,
(index) => ProjectDbEntity(
id: index,
name: 'Test $index project',
description: 'Test $index project description',
url: 'test$index.com',
imageUrl: '',
language: 'ES',
),
).toList();
await projectDao.insertProjects(projects);
expect((await projectDao.getProjects().first).length, projects.length);
});
test('Delete all projects from db, should return empty list', () async {
final projects = Iterable.generate(
10,
(index) => ProjectDbEntity(
id: index,
name: 'Test $index project',
description: 'Test $index project description',
url: 'test$index.com',
imageUrl: '',
language: 'ES',
),
).toList();
await projectDao.insertProjects(projects);
await projectDao.deleteAllProjects();
expect(await projectDao.getProjects().first, []);
});
test('Replace 2 projects from db, should return two new projects',
() async {
final projects = Iterable.generate(
2,
(index) => ProjectDbEntity(
id: index,
name: 'Test $index project',
description: 'Test $index project description',
url: 'test$index.com',
imageUrl: '',
language: 'ES',
),
).toList();
await projectDao.insertProjects(projects);
final replacement = projects
.map(
(e) => ProjectDbEntity(
id: Random().nextInt(100),
name: 'Test replace project',
description: 'Test project description',
url: 'tes.com',
imageUrl: '',
language: 'ES',
),
)
.toList();
await projectDao.replaceProjects(
replacement,
);
final list = await projectDao.getProjects().first;
bool result = true;
for (final element in list) {
if (replacement.firstWhereOrNull((e) => e.id == element.id) == null) {
result = false;
break;
}
}
expect(result, true);
});
});
}
57 changes: 57 additions & 0 deletions test/repositories/project/project_remote_source_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:dio/dio.dart';
import 'package:flutter_template/core/source/common/http_service.dart';
import 'package:flutter_template/core/source/project_remote_source.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

class HttpServiceMock extends Mock implements HttpService {}

void main() {
late HttpService httpService;
late ProjectRemoteSource projectRemoteSource;

setUp(() {
httpService = HttpServiceMock();
projectRemoteSource = ProjectRemoteSource(httpService);
});

test('Get projects from API should return one project', () async {
const urlGetProjects = 'rest/v1/projects?select=*';
final requestOptions = RequestOptions(path: urlGetProjects);

when(() => httpService.get(urlGetProjects)).thenAnswer(
(_) async => Response(
data: [
{
'id': 1,
'name': 'Test projects',
'description': 'Test project description',
'url': 'test.com',
'image_url': '',
'language': 'ES',
}
],
statusCode: 200,
requestOptions: requestOptions,
),
);

final result = await projectRemoteSource.getProjects();
expect(result.length, 1);
});
test('Get projects from empty API should return empty', () async {
const urlGetProjects = 'rest/v1/projects?select=*';
final requestOptions = RequestOptions(path: urlGetProjects);

when(() => httpService.get(urlGetProjects)).thenAnswer(
(_) async => Response(
data: [],
statusCode: 200,
requestOptions: requestOptions,
),
);

final result = await projectRemoteSource.getProjects();
expect(result.isEmpty, true);
});
}
100 changes: 100 additions & 0 deletions test/repositories/project/project_repository_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import 'package:flutter_template/core/model/db/repository_db_entity.dart';
import 'package:flutter_template/core/model/project.dart';
import 'package:flutter_template/core/repository/project_repository.dart';
import 'package:flutter_template/core/source/project_local_source.dart';
import 'package:flutter_template/core/source/project_remote_source.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

class ProjectLocalSourceMock extends Mock implements ProjectLocalSource {}

class ProjectRemoteSourceMock extends Mock implements ProjectRemoteSource {}

void main() {
late ProjectLocalSource projectLocalSource;
late ProjectRemoteSource projectRemoteSource;
late ProjectRepository projectRepository;

setUp(() async {
projectRepository = ProjectRepository(
projectLocalSource = ProjectLocalSourceMock(),
projectRemoteSource = ProjectRemoteSourceMock(),
);
});

test('get projects from empty state', () async {
when(() => projectLocalSource.getProjects()).thenAnswer(
(_) => Stream.value([]),
);
when(() => projectRemoteSource.getProjects()).thenAnswer(
(_) async => [],
);
//Default case for creating the repository
when(() => projectLocalSource.replaceProjects(any()))
.thenAnswer((_) async {});

final projects = await projectRepository.getProjects().first;

verify(() => projectLocalSource.getProjects()).called(1);
verify(() => projectRemoteSource.getProjects()).called(1);
verify(() => projectLocalSource.replaceProjects(any())).called(1);

expect(projects, []);
});

test(
'get projects stream from a loaded state, should return a stream that gives 2 values',
() async {
when(() => projectLocalSource.getProjects()).thenAnswer(
(_) => Stream.value(
[
ProjectDbEntity(
id: 1,
name: 'Test projects',
description: 'Test project description',
url: 'test.com',
imageUrl: '',
language: 'ES',
),
ProjectDbEntity(
id: 2,
name: 'Test projects',
description: 'Test project description',
url: 'test.com',
imageUrl: '',
language: 'ES',
),
],
),
);
when(() => projectLocalSource.replaceProjects(any()))
.thenAnswer((_) async {});
when(() => projectRemoteSource.getProjects()).thenAnswer(
(_) async => [
Project(
id: 1,
name: 'Test projects',
description: 'Test project description',
url: 'test.com',
imageUrl: '',
language: 'ES',
),
Project(
id: 2,
name: 'Test projects',
description: 'Test project description',
url: 'test.com',
imageUrl: '',
language: 'ES',
),
],
);

final list = await projectRepository.getProjects().first;
verify(() => projectLocalSource.getProjects()).called(1);
verify(() => projectRemoteSource.getProjects()).called(1);
verify(() => projectLocalSource.replaceProjects(any())).called(1);
expect(list?.length, 2);
},
);
}
Loading

0 comments on commit 78a968f

Please sign in to comment.