Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Session/9 Unit tests #22

Merged
merged 7 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions lib/model/weather.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@ class Weather {
UnknownWeather(),
),
};
} on UndefinedWeather catch (e) {
debugPrint(e.toString());
return const Failure(
UndefinedWeather(),
);
yuk1ch1 marked this conversation as resolved.
Show resolved Hide resolved
} on CheckedFromJsonException catch (e) {
debugPrint(e.toString());
return const Failure(
Expand Down
13 changes: 0 additions & 13 deletions lib/model/weather_condition.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_training/model/weather_exception.dart';

enum WeatherCondition {
sunny,
cloudy,
rainy;

factory WeatherCondition.from(String name) {
return WeatherCondition.values.singleWhere(
(element) => element.name == name,
orElse: () {
debugPrint(name);
throw const UndefinedWeather();
},
);
}
}
5 changes: 0 additions & 5 deletions lib/model/weather_exception.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ sealed class AppException implements Exception {
final String message;
}

/// Domainエラー
final class UndefinedWeather extends AppException {
const UndefinedWeather() : super(message: '天気情報の取得に失敗しました。適切な情報を取得できませんでした');
}

/// APIエラー
sealed class WeatherAPIException extends AppException {
const WeatherAPIException({required super.message});
Expand Down

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

8 changes: 8 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.5"
mockito:
dependency: "direct dev"
description:
name: mockito
sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917"
url: "https://pub.dev"
source: hosted
version: "5.4.4"
package_config:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dev_dependencies:
flutter_test:
sdk: flutter
json_serializable: ^6.7.1
mockito: ^5.4.4
riverpod_generator: ^2.4.0
riverpod_lint: ^2.3.10
yumemi_lints: ^1.7.0
Expand Down
102 changes: 102 additions & 0 deletions test/weather_screen_state_controller_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_training/model/weather.dart';
import 'package:flutter_training/model/weather_condition.dart';
import 'package:flutter_training/model/weather_exception.dart';
import 'package:flutter_training/model/weather_request.dart';
import 'package:flutter_training/model/weather_response.dart';
import 'package:flutter_training/presentation/screen/weather/weather_screen_state.dart';
import 'package:flutter_training/presentation/screen/weather/weather_screen_state_controller.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';

import 'weather_screen_state_controller_test.mocks.dart';

@GenerateNiceMocks([MockSpec<Weather>()])
void main() {
late MockWeather mock;
late ProviderContainer container;
late WeatherScreenStateController controller;
final request = WeatherRequest(
area: 'tokyo',
date: DateTime.now(),
);

setUp(() {
mock = MockWeather();
container = ProviderContainer(
overrides: [weatherProvider.overrideWithValue(mock)],
);
controller = container.read(weatherScreenStateControllerProvider.notifier);
});

tearDown(() => container.dispose());

group('WeatherScreenで利用する状態の更新テスト', () {
test('状態更新テスト_初期状態', () {
// Then
expect(
container.read(weatherScreenStateControllerProvider),
const WeatherScreenState.initial(),
);
});

test('状態更新テスト_更新成功した場合', () {
// Given
const dummyResponse = WeatherResponse(
weatherCondition: WeatherCondition.cloudy,
maxTemperature: 21,
minTemperature: 7,
);

const expectedResponse = WeatherScreenState.success(
weather: dummyResponse,
);

provideDummy<Result<WeatherResponse, AppException>>(
const Success(dummyResponse),
);
morikann marked this conversation as resolved.
Show resolved Hide resolved

when(mock.fetch(any)).thenReturn(
const Success<WeatherResponse, AppException>(dummyResponse),
);

// When
controller.update(request);

// Then
expect(
controller.state,
expectedResponse,
);
});

test('状態更新テスト_更新失敗した場合', () {
// Given
const expectedResponse = UnknownWeather();

provideDummy<Result<WeatherResponse, AppException>>(
const Failure(
UnknownWeather(),
),
);

when(mock.fetch(any)).thenReturn(
const Failure<WeatherResponse, AppException>(
expectedResponse,
),
);

// When
controller.update(request);

// Then
expect(
controller.state,
WeatherScreenState.error(
message: expectedResponse.message,
),
);
});
});
}
55 changes: 55 additions & 0 deletions test/weather_screen_state_controller_test.mocks.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Mocks generated by Mockito 5.4.4 from annotations
// in flutter_training/test/weather_screen_state_controller_test.dart.
// Do not manually edit this file.

// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:flutter_training/model/weather.dart' as _i2;
import 'package:flutter_training/model/weather_exception.dart' as _i4;
import 'package:flutter_training/model/weather_request.dart' as _i5;
import 'package:flutter_training/model/weather_response.dart' as _i3;
import 'package:mockito/mockito.dart' as _i1;
import 'package:mockito/src/dummies.dart' as _i6;

// 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 [Weather].
///
/// See the documentation for Mockito's code generation for more information.
class MockWeather extends _i1.Mock implements _i2.Weather {
@override
_i2.Result<_i3.WeatherResponse, _i4.AppException> fetch(
_i5.WeatherRequest? request) =>
(super.noSuchMethod(
Invocation.method(
#fetch,
[request],
),
returnValue:
_i6.dummyValue<_i2.Result<_i3.WeatherResponse, _i4.AppException>>(
this,
Invocation.method(
#fetch,
[request],
),
),
returnValueForMissingStub:
_i6.dummyValue<_i2.Result<_i3.WeatherResponse, _i4.AppException>>(
this,
Invocation.method(
#fetch,
[request],
),
),
) as _i2.Result<_i3.WeatherResponse, _i4.AppException>);
}
147 changes: 147 additions & 0 deletions test/weather_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_training/model/weather.dart';
import 'package:flutter_training/model/weather_condition.dart';
import 'package:flutter_training/model/weather_exception.dart';
import 'package:flutter_training/model/weather_request.dart';
import 'package:flutter_training/model/weather_response.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:yumemi_weather/yumemi_weather.dart';

import 'weather_test.mocks.dart';

@GenerateNiceMocks([MockSpec<YumemiWeather>()])
void main() {
late MockYumemiWeather mockYumemiWeather;
morikann marked this conversation as resolved.
Show resolved Hide resolved
late ProviderContainer container;
final request = WeatherRequest(
area: 'tokyo',
date: DateTime.now(),
);

setUp(() {
mockYumemiWeather = MockYumemiWeather();
container = ProviderContainer(
overrides: [
weatherProvider.overrideWithValue(
Weather(client: mockYumemiWeather),
),
],
);
});

tearDown(() {
container.dispose();
morikann marked this conversation as resolved.
Show resolved Hide resolved
});
morikann marked this conversation as resolved.
Show resolved Hide resolved

group('天気情報取得のテスト', () {
test(
'天気情報取得に成功_正常なレスポンスを返した場合',
() {
// Given
when(
mockYumemiWeather.fetchWeather(any),
).thenReturn(
'''
{
"weather_condition": "sunny",
"max_temperature": 30,
"min_temperature": 20,
"date": "2020-04-01T12:00:00+09:00"
}
''',
);

// When
final response = container.read(weatherProvider).fetch(request);

const expectedResponse = WeatherResponse(
weatherCondition: WeatherCondition.sunny,
maxTemperature: 30,
minTemperature: 20,
);

// Then
expect(
response,
isA<Success<WeatherResponse, AppException>>().having(
(success) => success.value,
'expected WeatherResponse',
expectedResponse,
),
);
},
);

test('天気情報取得に失敗_APIがInvalidParameter返した場合', () {
when(
mockYumemiWeather.fetchWeather(any),
).thenThrow(YumemiWeatherError.invalidParameter);

// When
final response = container.read(weatherProvider).fetch(request);

const expectedResponse = InvalidParameter();

// Then
expect(
response,
isA<Failure<WeatherResponse, AppException>>().having(
(failure) => failure.exception,
'AppException',
expectedResponse,
),
);
});

test('天気情報取得に失敗_APIが不明なエラーを返した場合', () {
when(
mockYumemiWeather.fetchWeather(any),
).thenThrow(YumemiWeatherError.unknown);

// When
final response = container.read(weatherProvider).fetch(request);

const expectedResponse = UnknownWeather();

// Then
expect(
response,
isA<Failure<WeatherResponse, AppException>>().having(
(failure) => failure.exception,
'AppException',
expectedResponse,
),
);
});

test('天気情報取得に失敗_APIがアプリ側未定義の天気を返した場合', () {
when(
mockYumemiWeather.fetchWeather(any),
).thenReturn('''
{
"weather_condition": "dummy",
"max_temperature": 30,
"min_temperature": 20,
"date": "2020-04-01T12:00:00+09:00"
}
''');

// When
final response = container.read(weatherProvider).fetch(request);

const expectedResponse = ResponseFormatException();

// Then
expect(
response,
isA<Failure<WeatherResponse, AppException>>().having(
(failure) => failure.exception,
'AppException',
expectedResponse,
),
);
});
});
}
Loading
Loading