Skip to content

Commit

Permalink
Add regression test
Browse files Browse the repository at this point in the history
  • Loading branch information
brylie committed Jul 27, 2024
1 parent 35af34b commit bbe6067
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 23 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Prerequisites](#prerequisites)
- [Running the App](#running-the-app)
- [Testing](#testing)
- [Generating mocks](#generating-mocks)
- [Contributing](#contributing)
- [Submit feedback and ideas](#submit-feedback-and-ideas)
- [Contribute code](#contribute-code)
Expand Down Expand Up @@ -89,6 +90,14 @@ flutter drive \
--target=integration_test/app_test.dart
```

### Generating mocks

To generate mocks for testing, use the following command:

```sh
dart run build_runner build
```

## Contributing

We welcome contributions from the community! Whether you're fixing bugs, adding new features, or improving documentation, your input is valuable.
Expand Down
29 changes: 6 additions & 23 deletions random_workout/lib/pages/workout_page.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:random_workout/widgets/exercise/add_exercise_options_dialog.dart';
import '../models/exercise.dart';
import '../providers/app_state.dart';
import '../data/all_exercises.dart';
Expand Down Expand Up @@ -46,12 +47,14 @@ class WorkoutPage extends StatelessWidget {
FloatingActionButton(
onPressed: () => appState.generateWorkout(category),
heroTag: null,
tooltip: 'Generate new workout',
child: const Icon(Icons.refresh),
),
const SizedBox(height: 16),
FloatingActionButton(
onPressed: () => _showAddExerciseDialog(context),
heroTag: null,
tooltip: 'Add exercise',
child: const Icon(Icons.add),
),
],
Expand All @@ -63,29 +66,9 @@ class WorkoutPage extends StatelessWidget {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Add Exercise'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
ElevatedButton(
child: const Text('Choose from pre-defined exercises'),
onPressed: () {
Navigator.of(context).pop();
_showPreDefinedExercisesDialog(context);
},
),
const SizedBox(height: 20),
ElevatedButton(
child: const Text('Create a custom exercise'),
onPressed: () {
Navigator.of(context).pop();
_showCreateExerciseDialog(context);
},
),
],
),
),
return AddExerciseOptionsDialog(
onChoosePreDefined: () => _showPreDefinedExercisesDialog(context),
onCreateCustom: () => _showCreateExerciseDialog(context),
);
},
);
Expand Down
3 changes: 3 additions & 0 deletions random_workout/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ version: 1.0.0+1

environment:
sdk: '>=3.4.3 <4.0.0'

dependencies:
flutter:
sdk: flutter
Expand All @@ -32,6 +33,8 @@ dev_dependencies:
test: ^1.16.0
integration_test:
sdk: flutter
mockito: ^5.0.0
build_runner: ^2.1.0

flutter:
uses-material-design: true
Expand Down
6 changes: 6 additions & 0 deletions random_workout/test/mocks/mock_app_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'package:mockito/annotations.dart';
import 'package:random_workout/providers/app_state.dart';
// import 'package:random_workout/models/exercise.dart';

@GenerateNiceMocks([MockSpec<AppState>()])
void main() {}
121 changes: 121 additions & 0 deletions random_workout/test/mocks/mock_app_state.mocks.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Mocks generated by Mockito 5.4.4 from annotations
// in random_workout/test/mocks/mock_app_state.dart.
// Do not manually edit this file.

// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:ui' as _i4;

import 'package:mockito/mockito.dart' as _i1;
import 'package:random_workout/models/exercise.dart' as _i3;
import 'package:random_workout/providers/app_state.dart' as _i2;

// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: deprecated_member_use
// ignore_for_file: deprecated_member_use_from_same_package
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class

/// A class which mocks [AppState].
///
/// See the documentation for Mockito's code generation for more information.
class MockAppState extends _i1.Mock implements _i2.AppState {
@override
bool get isDarkMode => (super.noSuchMethod(
Invocation.getter(#isDarkMode),
returnValue: false,
returnValueForMissingStub: false,
) as bool);

@override
List<_i3.Exercise> get currentWorkout => (super.noSuchMethod(
Invocation.getter(#currentWorkout),
returnValue: <_i3.Exercise>[],
returnValueForMissingStub: <_i3.Exercise>[],
) as List<_i3.Exercise>);

@override
bool get hasListeners => (super.noSuchMethod(
Invocation.getter(#hasListeners),
returnValue: false,
returnValueForMissingStub: false,
) as bool);

@override
void toggleTheme() => super.noSuchMethod(
Invocation.method(
#toggleTheme,
[],
),
returnValueForMissingStub: null,
);

@override
void generateWorkout(_i3.ExerciseCategory? category) => super.noSuchMethod(
Invocation.method(
#generateWorkout,
[category],
),
returnValueForMissingStub: null,
);

@override
void removeExercise(int? index) => super.noSuchMethod(
Invocation.method(
#removeExercise,
[index],
),
returnValueForMissingStub: null,
);

@override
void addExercise(_i3.Exercise? exercise) => super.noSuchMethod(
Invocation.method(
#addExercise,
[exercise],
),
returnValueForMissingStub: null,
);

@override
void addListener(_i4.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#addListener,
[listener],
),
returnValueForMissingStub: null,
);

@override
void removeListener(_i4.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#removeListener,
[listener],
),
returnValueForMissingStub: null,
);

@override
void dispose() => super.noSuchMethod(
Invocation.method(
#dispose,
[],
),
returnValueForMissingStub: null,
);

@override
void notifyListeners() => super.noSuchMethod(
Invocation.method(
#notifyListeners,
[],
),
returnValueForMissingStub: null,
);
}
99 changes: 99 additions & 0 deletions random_workout/test/pages/workout_page_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';
import 'package:random_workout/models/exercise.dart';
import 'package:random_workout/pages/workout_page.dart';
import 'package:random_workout/providers/app_state.dart';
import 'package:random_workout/widgets/exercise/add_exercise_options_dialog.dart';
import '../mocks/mock_app_state.mocks.dart';

void main() {
late MockAppState mockAppState;

setUp(() {
mockAppState = MockAppState();

// Set up default stubs
when(mockAppState.isDarkMode).thenReturn(false);
when(mockAppState.currentWorkout).thenReturn([]);
when(mockAppState.generateWorkout(any)).thenReturn(null);
});

testWidgets('Add exercise button opens AddExerciseOptionsDialog',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: ChangeNotifierProvider<AppState>.value(
value: mockAppState,
child: const WorkoutPage(category: ExerciseCategory.strength),
),
),
);

expect(find.byType(WorkoutPage), findsOneWidget);

final addButton = find.byTooltip('Add exercise');
expect(addButton, findsOneWidget);

await tester.tap(addButton);
await tester.pumpAndSettle();

expect(find.byType(AddExerciseOptionsDialog), findsOneWidget);
expect(find.text('Add Exercise'), findsOneWidget);
expect(find.text('Choose from pre-defined exercises'), findsOneWidget);
expect(find.text('Create a custom exercise'), findsOneWidget);

verifyNever(mockAppState.generateWorkout(any));
verifyNever(mockAppState.addExercise(any));
});

testWidgets('Generate new workout button calls generateWorkout',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: ChangeNotifierProvider<AppState>.value(
value: mockAppState,
child: const WorkoutPage(category: ExerciseCategory.strength),
),
),
);

final generateButton = find.byTooltip('Generate new workout');
expect(generateButton, findsOneWidget);

await tester.tap(generateButton);
await tester.pumpAndSettle();

verify(mockAppState.generateWorkout(ExerciseCategory.strength)).called(1);
verifyNever(mockAppState.addExercise(any));
});

testWidgets('WorkoutPage displays current workout',
(WidgetTester tester) async {
final testExercises = [
Exercise(
name: 'Push-ups',
description: 'Do push-ups',
category: ExerciseCategory.strength),
Exercise(
name: 'Squats',
description: 'Do squats',
category: ExerciseCategory.strength),
];

when(mockAppState.currentWorkout).thenReturn(testExercises);

await tester.pumpWidget(
MaterialApp(
home: ChangeNotifierProvider<AppState>.value(
value: mockAppState,
child: const WorkoutPage(category: ExerciseCategory.strength),
),
),
);

expect(find.text('Push-ups'), findsOneWidget);
expect(find.text('Squats'), findsOneWidget);
});
}

0 comments on commit bbe6067

Please sign in to comment.