create unit tests fro findByFilters and generate mocks

This commit is contained in:
Yair Chen
2021-09-15 16:46:04 +03:00
parent dd50e23dcd
commit b11fcda216
12 changed files with 327 additions and 41 deletions

View File

@@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:wger/models/exercises/category.dart';
import 'package:wger/models/exercises/comment.dart';
@@ -27,7 +28,7 @@ import 'package:wger/models/exercises/muscle.dart';
part 'base.g.dart';
@JsonSerializable(explicitToJson: true)
class ExerciseBase {
class ExerciseBase extends Equatable {
@JsonKey(required: true)
final int id;
@@ -107,4 +108,16 @@ class ExerciseBase {
// Boilerplate
factory ExerciseBase.fromJson(Map<String, dynamic> json) => _$ExerciseBaseFromJson(json);
Map<String, dynamic> toJson() => _$ExerciseBaseToJson(this);
@override
List<Object?> get props => [
id,
uuid,
creationDate,
updateDate,
category,
equipment,
muscles,
musclesSecondary,
];
}

View File

@@ -14,7 +14,8 @@ ExerciseCategory _$ExerciseCategoryFromJson(Map<String, dynamic> json) {
);
}
Map<String, dynamic> _$ExerciseCategoryToJson(ExerciseCategory instance) => <String, dynamic>{
Map<String, dynamic> _$ExerciseCategoryToJson(ExerciseCategory instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
};

View File

@@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:wger/models/exercises/base.dart';
import 'package:wger/models/exercises/category.dart';
@@ -28,7 +29,7 @@ import 'package:wger/models/exercises/muscle.dart';
part 'exercise.g.dart';
@JsonSerializable()
class Exercise {
class Exercise extends Equatable {
@JsonKey(required: true)
final int id;
@@ -88,4 +89,16 @@ class Exercise {
// Boilerplate
factory Exercise.fromJson(Map<String, dynamic> json) => _$ExerciseFromJson(json);
Map<String, dynamic> toJson() => _$ExerciseToJson(this);
@override
List<Object?> get props => [
id,
baseId,
uuid,
languageId,
creationDate,
name,
description,
base,
];
}

View File

@@ -7,7 +7,8 @@ part of 'image.dart';
// **************************************************************************
ExerciseImage _$ExerciseImageFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const ['id', 'uuid', 'exercise_base', 'image']);
$checkKeys(json,
requiredKeys: const ['id', 'uuid', 'exercise_base', 'image']);
return ExerciseImage(
id: json['id'] as int,
uuid: json['uuid'] as String,
@@ -17,7 +18,8 @@ ExerciseImage _$ExerciseImageFromJson(Map<String, dynamic> json) {
);
}
Map<String, dynamic> _$ExerciseImageToJson(ExerciseImage instance) => <String, dynamic>{
Map<String, dynamic> _$ExerciseImageToJson(ExerciseImage instance) =>
<String, dynamic>{
'id': instance.id,
'uuid': instance.uuid,
'exercise_base': instance.exerciseBaseId,

View File

@@ -52,16 +52,21 @@ class ExercisesProvider with ChangeNotifier {
static const _languageUrlPath = 'language';
List<ExerciseBase> _exerciseBases = [];
List<Exercise> _exercises = [];
List<ExerciseCategory> _categories = [];
List<Muscle> _muscles = [];
List<Equipment> _equipment = [];
List<Language> _languages = [];
List<Exercise> _exercises = [];
set exercises(List<Exercise> exercises) {
_exercises = exercises;
}
Filters? _filters;
Filters? get filters => _filters;
set filters(Filters? newFilters) {
Future<void> setFilters(Filters? newFilters) async {
_filters = newFilters;
this._findByFilters();
await this.findByFilters();
}
List<Exercise>? _filteredExercises = [];
@@ -80,32 +85,40 @@ class ExercisesProvider with ChangeNotifier {
void _initFilters() {
if (_muscles.isEmpty || _equipment.isEmpty || _filters != null) return;
filters = Filters(
exerciseCategories: FilterCategory<ExerciseCategory>(
title: 'Muscle Groups',
items: Map.fromEntries(
_categories.map(
(category) => MapEntry<ExerciseCategory, bool>(category, false),
this.setFilters(
Filters(
exerciseCategories: FilterCategory<ExerciseCategory>(
title: 'Muscle Groups',
items: Map.fromEntries(
_categories.map(
(category) => MapEntry<ExerciseCategory, bool>(category, false),
),
),
),
),
equipment: FilterCategory<Equipment>(
title: 'Equipment',
items: Map.fromEntries(
_equipment.map(
(singleEquipment) => MapEntry<Equipment, bool>(singleEquipment, false),
equipment: FilterCategory<Equipment>(
title: 'Equipment',
items: Map.fromEntries(
_equipment.map(
(singleEquipment) => MapEntry<Equipment, bool>(singleEquipment, false),
),
),
),
),
);
}
Future<void> _findByFilters() async {
Future<void> findByFilters() async {
// Filters not initalized
if (filters == null) filteredExercises = [];
if (filters == null) {
filteredExercises = [];
return;
}
// Filters are initialized and nothing is marked
if (filters!.isNothingMarked && filters!.searchTerm.length <= 1) filteredExercises = items;
if (filters!.isNothingMarked && filters!.searchTerm.length <= 1) {
filteredExercises = items;
return;
}
filteredExercises = null;
@@ -331,6 +344,7 @@ class ExercisesProvider with ChangeNotifier {
final exerciseBaseData = await baseProvider.fetch(
baseProvider.makeUrl(_exerciseBaseUrlPath, query: {'limit': '1000'}),
);
final exerciseTranslationData = await baseProvider.fetch(
baseProvider.makeUrl(
_exerciseUrlPath,

View File

@@ -56,7 +56,8 @@ class _ExerciseFilterModalBodyState extends State<ExerciseFilterModalBody> {
onChanged: (_) {
setState(() {
filterCategory.items.update(currentEntry.key, (value) => !value);
Provider.of<ExercisesProvider>(context, listen: false).filters = filters;
Provider.of<ExercisesProvider>(context, listen: false)
.setFilters(filters);
});
},
);

View File

@@ -24,7 +24,8 @@ class _FilterRowState extends State<FilterRow> {
() {
final provider = Provider.of<ExercisesProvider>(context, listen: false);
if (provider.filters!.searchTerm != _exerciseNameController.text) {
provider.filters = provider.filters!.copyWith(searchTerm: _exerciseNameController.text);
provider
.setFilters(provider.filters!.copyWith(searchTerm: _exerciseNameController.text));
}
},
);

View File

@@ -1,16 +1,20 @@
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart';
import 'package:mockito/mockito.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:wger/exceptions/no_such_entry_exception.dart';
import 'package:wger/models/exercises/category.dart';
import 'package:wger/models/exercises/equipment.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/models/exercises/language.dart';
import 'package:wger/models/exercises/muscle.dart';
import 'package:wger/providers/exercises.dart';
import '../fixtures/fixture_reader.dart';
import '../measurements/measurement_provider_test.mocks.dart';
import '../../test_data/exercises.dart' as data;
main() {
late MockWgerBaseProvider mockBaseProvider;
@@ -20,6 +24,8 @@ main() {
String muscleUrl = 'muscle';
String equipmentUrl = 'equipment';
String languageUrl = 'language';
String exerciseBaseUrl = 'exercise-base';
String searchExerciseUrl = 'exercise/search';
Uri tCategoryEntriesUri = Uri(
scheme: 'http',
@@ -45,6 +51,12 @@ main() {
path: 'api/v2/' + languageUrl + '/',
);
Uri tSearchByNameUri = Uri(
scheme: 'http',
host: 'localhost',
path: 'api/v2/$searchExerciseUrl/',
);
final category1 = ExerciseCategory(id: 1, name: 'Arms');
final muscle1 = Muscle(id: 1, name: 'Biceps brachii', isFront: true);
final equipment1 = Equipment(id: 1, name: 'Barbell');
@@ -148,4 +160,185 @@ main() {
expect(() => provider.findLanguageById(10), throwsA(isA<NoSuchEntryException>()));
});
});
group('findByFilters', () {
test('Filters are null', () async {
// arrange
Filters? currentFilters;
// arrange and act
await provider.setFilters(currentFilters);
// assert
verifyNever(provider.baseProvider.fetch(tSearchByNameUri));
expect(provider.filteredExercises, isEmpty);
});
group('Filters are not null', () {
late Filters filters;
setUp(() {
SharedPreferences.setMockInitialValues({});
filters = Filters(
exerciseCategories: FilterCategory<ExerciseCategory>(title: 'Muscle Groups', items: {}),
equipment: FilterCategory<Equipment>(title: 'Equipment', items: {}),
);
provider.exercises = data.getExercise();
});
test('Nothing is selected with no search term', () async {
// arrange
Filters currentFilters = filters;
// act
await provider.setFilters(currentFilters);
// assert
verifyNever(provider.baseProvider.fetch(tSearchByNameUri));
expect(
provider.filteredExercises,
data.getExercise(),
);
});
test('A muscle is selected with no search term. Should find results', () async {
// arrange
Filters tFilters = filters.copyWith(
exerciseCategories: filters.exerciseCategories.copyWith(items: {category1: true}),
);
// act
await provider.setFilters(tFilters);
// assert
verifyNever(provider.baseProvider.fetch(tSearchByNameUri));
expect(provider.filteredExercises, [data.getExercise()[0]]);
});
test('A muscle is selected with no search term. Should not find results', () async {
// arragne
Filters tFilters = filters.copyWith(
exerciseCategories: filters.exerciseCategories.copyWith(items: {data.category4: true}),
);
// act
await provider.setFilters(tFilters);
// assert
verifyNever(provider.baseProvider.fetch(tSearchByNameUri));
expect(provider.filteredExercises, isEmpty);
});
test('An equipment is selected with no search term. Should find results', () async {
// arragne
Filters tFilters = filters.copyWith(
equipment: filters.equipment.copyWith(items: {data.equipment1: true}),
);
// act
await provider.setFilters(tFilters);
// assert
verifyNever(provider.baseProvider.fetch(tSearchByNameUri));
expect(provider.filteredExercises, [data.getExercise()[0]]);
});
test('An equipment is selected with no search term. Should not find results', () async {
// arragne
Filters tFilters = filters.copyWith(
equipment: filters.equipment.copyWith(items: {data.equipment3: true}),
);
// act
await provider.setFilters(tFilters);
// assert
verifyNever(provider.baseProvider.fetch(tSearchByNameUri));
expect(provider.filteredExercises, isEmpty);
});
test('A muscle and equipment is selected and there is a match', () async {
// arrange
Filters tFilters = filters.copyWith(
exerciseCategories: filters.exerciseCategories.copyWith(items: {data.category2: true}),
equipment: filters.equipment.copyWith(items: {data.equipment2: true}),
);
// act
await provider.setFilters(tFilters);
// assert
verifyNever(provider.baseProvider.fetch(tSearchByNameUri));
expect(provider.filteredExercises, [data.getExercise()[1]]);
});
test('A muscle and equipment is selected but no match', () async {
// arrange
Filters tFilters = filters.copyWith(
exerciseCategories: filters.exerciseCategories.copyWith(items: {data.category2: true}),
equipment: filters.equipment.copyWith(items: {equipment1: true}),
);
// act
await provider.setFilters(tFilters);
// assert
verifyNever(provider.baseProvider.fetch(tSearchByNameUri));
expect(provider.filteredExercises, isEmpty);
});
group('Search term', () {
late Uri tSearchByNameUri;
setUp(() {
String tSearchTerm = 'press';
String tSearchLanguage = 'en';
Map<String, dynamic> query = {'term': tSearchTerm, 'language': tSearchLanguage};
tSearchByNameUri = Uri(
scheme: 'http',
host: 'localhost',
path: 'api/v2/$searchExerciseUrl/',
queryParameters: query,
);
Map<String, dynamic> tSearchResponse =
jsonDecode(fixture('exercise_search_entries.json'));
// Mock exercise search
when(
mockBaseProvider.makeUrl(
searchExerciseUrl,
query: {'term': tSearchTerm, 'language': tSearchLanguage},
),
).thenReturn(tSearchByNameUri);
when(mockBaseProvider.fetch(tSearchByNameUri)).thenAnswer((_) async => tSearchResponse);
});
test('Should find results from search term', () async {
// arrange
Filters tFilters = filters.copyWith(searchTerm: 'press');
// act
await provider.setFilters(tFilters);
// assert
verify(provider.baseProvider.fetch(tSearchByNameUri)).called(1);
expect(provider.filteredExercises, [data.getExercise()[0], data.getExercise()[1]]);
});
test('Should find items from selection but should filter them by search term', () async {
// arrange
Filters tFilters = filters.copyWith(
searchTerm: 'press',
exerciseCategories: filters.exerciseCategories.copyWith(items: {data.category3: true}),
);
// act
await provider.setFilters(tFilters);
// assert
verify(provider.baseProvider.fetch(tSearchByNameUri)).called(1);
expect(provider.filteredExercises, isEmpty);
});
});
});
});
}

View File

@@ -0,0 +1,24 @@
{
"suggestions": [
{
"value": "Bench Press Narrow Grip",
"data": {
"id": 1,
"name": "test exercise 1",
"category": "Arms",
"image": "/media/exercise-images/76/Narrow-grip-bench-press-2.png",
"image_thumbnail": "/media/exercise-images/76/Narrow-grip-bench-press-2.png.30x30_q85_crop-smart.png"
}
},
{
"value": "Close-grip Bench Press",
"data": {
"id": 2,
"name": "test exercise 2",
"category": "Arms",
"image": null,
"image_thumbnail": null
}
}
]
}

View File

@@ -52,6 +52,14 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
(super.noSuchMethod(Invocation.getter(#baseProvider),
returnValue: _FakeWgerBaseProvider_0()) as _i2.WgerBaseProvider);
@override
set exercises(List<_i3.Exercise>? exercises) =>
super.noSuchMethod(Invocation.setter(#exercises, exercises),
returnValueForMissingStub: null);
@override
set filteredExercises(List<_i3.Exercise>? newfilteredExercises) => super
.noSuchMethod(Invocation.setter(#filteredExercises, newfilteredExercises),
returnValueForMissingStub: null);
@override
List<_i3.Exercise> get items => (super.noSuchMethod(Invocation.getter(#items),
returnValue: <_i3.Exercise>[]) as List<_i3.Exercise>);
@override
@@ -63,12 +71,15 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
as bool);
@override
void initFilters() => super.noSuchMethod(Invocation.method(#initFilters, []),
returnValueForMissingStub: null);
_i10.Future<void> setFilters(_i9.Filters? newFilters) => (super.noSuchMethod(
Invocation.method(#setFilters, [newFilters]),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
@override
List<_i3.Exercise> findByFilters() =>
(super.noSuchMethod(Invocation.method(#findByFilters, []),
returnValue: <_i3.Exercise>[]) as List<_i3.Exercise>);
_i10.Future<void> findByFilters() => (super.noSuchMethod(
Invocation.method(#findByFilters, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
@override
List<_i3.Exercise> findByCategory(_i4.ExerciseCategory? category) =>
(super.noSuchMethod(Invocation.method(#findByCategory, [category]),
@@ -130,12 +141,12 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
@override
_i10.Future<List<dynamic>> searchExercise(String? name,
_i10.Future<List<_i3.Exercise>> searchExercise(String? name,
[String? languageCode = r'en']) =>
(super.noSuchMethod(
Invocation.method(#searchExercise, [name, languageCode]),
returnValue: Future<List<dynamic>>.value(<dynamic>[]))
as _i10.Future<List<dynamic>>);
returnValue: Future<List<_i3.Exercise>>.value(<_i3.Exercise>[]))
as _i10.Future<List<_i3.Exercise>>);
@override
String toString() => super.toString();
@override

View File

@@ -52,6 +52,14 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
(super.noSuchMethod(Invocation.getter(#baseProvider),
returnValue: _FakeWgerBaseProvider_0()) as _i2.WgerBaseProvider);
@override
set exercises(List<_i3.Exercise>? exercises) =>
super.noSuchMethod(Invocation.setter(#exercises, exercises),
returnValueForMissingStub: null);
@override
set filteredExercises(List<_i3.Exercise>? newfilteredExercises) => super
.noSuchMethod(Invocation.setter(#filteredExercises, newfilteredExercises),
returnValueForMissingStub: null);
@override
List<_i3.Exercise> get items => (super.noSuchMethod(Invocation.getter(#items),
returnValue: <_i3.Exercise>[]) as List<_i3.Exercise>);
@override
@@ -63,12 +71,15 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
as bool);
@override
void initFilters() => super.noSuchMethod(Invocation.method(#initFilters, []),
returnValueForMissingStub: null);
_i10.Future<void> setFilters(_i9.Filters? newFilters) => (super.noSuchMethod(
Invocation.method(#setFilters, [newFilters]),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
@override
List<_i3.Exercise> findByFilters() =>
(super.noSuchMethod(Invocation.method(#findByFilters, []),
returnValue: <_i3.Exercise>[]) as List<_i3.Exercise>);
_i10.Future<void> findByFilters() => (super.noSuchMethod(
Invocation.method(#findByFilters, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
@override
List<_i3.Exercise> findByCategory(_i4.ExerciseCategory? category) =>
(super.noSuchMethod(Invocation.method(#findByCategory, [category]),
@@ -130,12 +141,12 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
@override
_i10.Future<List<dynamic>> searchExercise(String? name,
_i10.Future<List<_i3.Exercise>> searchExercise(String? name,
[String? languageCode = r'en']) =>
(super.noSuchMethod(
Invocation.method(#searchExercise, [name, languageCode]),
returnValue: Future<List<dynamic>>.value(<dynamic>[]))
as _i10.Future<List<dynamic>>);
returnValue: Future<List<_i3.Exercise>>.value(<_i3.Exercise>[]))
as _i10.Future<List<_i3.Exercise>>);
@override
String toString() => super.toString();
@override

View File

@@ -29,9 +29,11 @@ const muscle3 = Muscle(id: 3, name: 'Booty', isFront: false);
const category1 = ExerciseCategory(id: 1, name: 'Arms');
const category2 = ExerciseCategory(id: 2, name: 'Legs');
const category3 = ExerciseCategory(id: 3, name: 'Abs');
const category4 = ExerciseCategory(id: 4, name: 'Shoulders');
const equipment1 = Equipment(id: 1, name: 'Bench');
const equipment2 = Equipment(id: 1, name: 'Dumbbell');
const equipment3 = Equipment(id: 2, name: 'Matress');
List<Exercise> getExercise() {
final base1 = ExerciseBase(