Repository: hidekitux/yumemi-flutter-training
Pull request #23: Session9 ユニットテスト
Author: login: hidekitux
Created: 2025-04-22T05:16:39Z | Merged: 2025-04-24T06:28:01Z

## 課題

close #10 

## 対応箇所

<!--
課題のどこに対応したか１つ１つ確認しましょう。
-->

- [x] [yumemi_weather](https://yumemi-inc.github.io/flutter-training-template/) API の呼び出しから Widget へ通知する部分までの [Unit tests](https://docs.flutter.dev/testing#unit-tests) を書く
  - 依存しているものが、成功・失敗するケースも網羅する
- [x] 余力があれば、JSON のエンコード・デコードの [Unit tests](https://docs.flutter.dev/testing#unit-tests) も書く

---

- [x] mockitoを導入した (af55b6c0395d8c4614be06578cafffc9bf64cf99, 5827ee198a82248b2dbbf2d8f4892d4908278a76)
- [x] CIにLCOV, [Octocov](https://github.com/k1LoW/octocov)を設定した (f360463bd574df5389f89be168e8252179a51ca6)
- [x] `weather_view_model_test.dart`にて、`reloadWeather`関数に関する`verify(mockWeatherRepository.getWeather(expectedWeatherTarget)).called(1)`が通らない
  - `reloadWeather`関数内で`DateTime.now()`を使っているため、ほぼdateが一致しない
  - 現状は`verify(mockWeatherRepository.getWeather(any)).called(1)`で対応している

```dart
  // weather_view_model.dart
  void reloadWeather() {
    final result = ref
        .read(reloadWeatherUseCaseProvider)
        .call(WeatherTargetEntity(area: 'Tokyo', date: DateTime.now()));
```

## 動作

動作に変更なし


Linked issue #10: Session9
# [Unit tests] を書く

Flutter のテストは次の３つに分類されます。

- [Unit tests]
- [Widget tests]
- [Integration tests]

[Unit tests] は、他のテストより依存関係を少なくすることができるため、実装やメンテナンスコストを低く抑えることができます。

実際に [Unit tests] を書いていきましょう。

## 課題

- [yumemi_weather] API の呼び出しから Widget へ通知する部分までの [Unit tests] を書く
    - 依存しているものが、成功・失敗するケースも網羅する
- 余力があれば、JSON のエンコード・デコードの [Unit tests] も書く

※ テストコードを書くにあたって、依存関係を見直すなどのリファクタリングを行っても問題ありません。

## ヒント

テストを書くときは、依存しているものは何で、前提条件と完了条件は何なのかを明確にすることが重要です。

また、[mockito | REAMDME] を使って依存しているものをモックすることで、様々なテストケースに柔軟かつ簡単に対応できるようになります。

## 参考資料

- [Testing your providers]

<!-- Links -->

[Unit tests]: https://docs.flutter.dev/testing#unit-tests

[Widget tests]: https://docs.flutter.dev/testing#widget-tests

[Integration tests]: https://docs.flutter.dev/testing#integration-tests

[yumemi_weather]: https://yumemi-inc.github.io/flutter-training-template/

[mockito | REAMDME]: https://pub.dev/packages/mockito

[Testing your providers]: https://riverpod.dev/docs/essentials/testing


Diff:
diff --git a/.gitattributes b/.gitattributes
index 9c25cbb..2710fdb 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,4 @@
 *.freezed.dart linguist-generated=true
 *.g.dart linguist-generated=true
 *.gen.dart linguist-generated=true
+*.mocks.dart linguist-generated=true
diff --git a/.github/workflows/check-pr.yaml b/.github/workflows/check-pr.yaml
index 1bdc39a..350390d 100644
--- a/.github/workflows/check-pr.yaml
+++ b/.github/workflows/check-pr.yaml
@@ -71,7 +71,7 @@ jobs:
           fatal-infos: true
 
       - name: Run test
-        run: flutter test --no-pub --machine > test_report.log
+        run: flutter test --coverage --no-pub --machine > test_report.log
         continue-on-error: true
 
       # https://github.com/dorny/test-reporter
@@ -82,3 +82,16 @@ jobs:
           path: 'test_report.log'
           reporter: 'flutter-json'
           fail-on-error: 'true'
+
+      - name: Setup LCOV
+        uses: hrishikesh-kadam/setup-lcov@v1
+
+      - name: Remove auto generated files from coverage
+        run: |
+          lcov --remove coverage/lcov.info \
+          'lib/**/*.g.dart' 'lib/**/*.freezed.dart' 'lib/**/*.gen.dart' 'lib/**/*.mocks.dart' \
+          -o coverage/lcov.info \
+          --ignore-errors unused
+
+      - name: Report coverage to GitHub
+        uses: k1LoW/octocov-action@v1
diff --git a/.octocov.yml b/.octocov.yml
new file mode 100644
index 0000000..b386bc1
--- /dev/null
+++ b/.octocov.yml
@@ -0,0 +1,16 @@
+# generated by octocov init
+coverage:
+  if: true
+testExecutionTime:
+  if: true
+diff:
+  datastores:
+    - artifact://${GITHUB_REPOSITORY}
+comment:
+  if: is_pull_request
+summary:
+  if: true
+report:
+  if: is_default_branch
+  datastores:
+    - artifact://${GITHUB_REPOSITORY}
diff --git a/lib/presentation/weather/view_models/weather_view_model.dart b/lib/presentation/weather/view_models/weather_view_model.dart
index 58a60dd..36b6f18 100644
--- a/lib/presentation/weather/view_models/weather_view_model.dart
+++ b/lib/presentation/weather/view_models/weather_view_model.dart
@@ -14,10 +14,8 @@ class WeatherViewModel extends _$WeatherViewModel {
   /// 天気予報を取得し、実行結果に応じてstateを更新する
   /// - 成功時: エラーメッセージを消去し、取得した天気予報でstateを更新する
   /// - 失敗時: 元の天気予報を保持しながら、エラーメッセージでstateを更新する
-  void reloadWeather() {
-    final result = ref
-        .read(reloadWeatherUseCaseProvider)
-        .call(WeatherTargetEntity(area: 'Tokyo', date: DateTime.now()));
+  void reloadWeather(WeatherTargetEntity weatherTarget) {
+    final result = ref.read(reloadWeatherUseCaseProvider).call(weatherTarget);
 
     switch (result) {
       case Success(value: final weatherInfo):
diff --git a/lib/presentation/weather/view_models/weather_view_model.g.dart b/lib/presentation/weather/view_models/weather_view_model.g.dart
index e2bdecf..614466d 100644
--- a/lib/presentation/weather/view_models/weather_view_model.g.dart
+++ b/lib/presentation/weather/view_models/weather_view_model.g.dart
@@ -8,7 +8,7 @@ part of 'weather_view_model.dart';
 // RiverpodGenerator
 // **************************************************************************
 
-String _$weatherViewModelHash() => r'2363ff682c067b06b691eab76e5a43071f90d620';
+String _$weatherViewModelHash() => r'd78931f53b2ae37a5bd1625093138f9ed0fd46bd';
 
 /// See also [WeatherViewModel].
 @ProviderFor(WeatherViewModel)
diff --git a/lib/presentation/weather/views/weather_view.dart b/lib/presentation/weather/views/weather_view.dart
index 55942c3..07bc2f1 100644
--- a/lib/presentation/weather/views/weather_view.dart
+++ b/lib/presentation/weather/views/weather_view.dart
@@ -3,6 +3,7 @@ import 'dart:async';
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:flutter_svg/flutter_svg.dart';
+import 'package:flutter_training/domain/weather/entities/weather_target_entity.dart';
 import 'package:flutter_training/presentation/common/components/error_dialog.dart';
 import 'package:flutter_training/presentation/weather/components/temperature_indicator.dart';
 import 'package:flutter_training/presentation/weather/components/weather_action_button.dart';
@@ -80,10 +81,14 @@ class WeatherView extends ConsumerWidget {
                       WeatherActionButton(
                         label: 'Reload',
                         onPressed:
-                            () =>
-                                ref
-                                    .read(weatherViewModelProvider.notifier)
-                                    .reloadWeather(),
+                            () => ref
+                                .read(weatherViewModelProvider.notifier)
+                                .reloadWeather(
+                                  WeatherTargetEntity(
+                                    area: 'Tokyo',
+                                    date: DateTime.now(),
+                                  ),
+                                ),
                       ),
                     ],
                   ),
diff --git a/pubspec.lock b/pubspec.lock
index 76d36d9..5f60bec 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -29,10 +29,10 @@ packages:
     dependency: transitive
     description:
       name: archive
-      sha256: "7dcbd0f87fe5f61cb28da39a1a8b70dbc106e2fe0516f7836eb7bb2948481a12"
+      sha256: a7f37ff061d7abc2fcf213554b9dcaca713c5853afa5c065c44888bc9ccaf813
       url: "https://pub.dev"
     source: hosted
-    version: "4.0.5"
+    version: "4.0.6"
   args:
     dependency: transitive
     description:
@@ -447,10 +447,10 @@ packages:
     dependency: "direct dev"
     description:
       name: json_serializable
-      sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a"
+      sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
       url: "https://pub.dev"
     source: hosted
-    version: "6.9.4"
+    version: "6.9.5"
   leak_tracker:
     dependency: transitive
     description:
@@ -515,6 +515,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.0.0"
+  mockito:
+    dependency: "direct dev"
+    description:
+      name: mockito
+      sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6
+      url: "https://pub.dev"
+    source: hosted
+    version: "5.4.5"
   package_config:
     dependency: transitive
     description:
@@ -559,10 +567,10 @@ packages:
     dependency: transitive
     description:
       name: posix
-      sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a
+      sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62
       url: "https://pub.dev"
     source: hosted
-    version: "6.0.1"
+    version: "6.0.2"
   pub_semver:
     dependency: transitive
     description:
@@ -828,18 +836,18 @@ packages:
     dependency: transitive
     description:
       name: web_socket
-      sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
+      sha256: bfe6f435f6ec49cb6c01da1e275ae4228719e59a6b067048c51e72d9d63bcc4b
       url: "https://pub.dev"
     source: hosted
-    version: "0.1.6"
+    version: "1.0.0"
   web_socket_channel:
     dependency: transitive
     description:
       name: web_socket_channel
-      sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5"
+      sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.2"
+    version: "3.0.3"
   xml:
     dependency: transitive
     description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 9471815..c65cf61 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -30,6 +30,7 @@ dev_dependencies:
   freezed: ^3.0.6
   freezed_lint: ^0.0.9
   json_serializable: ^6.9.4
+  mockito: ^5.4.5
   riverpod_generator: ^2.6.5
   riverpod_lint: ^2.6.5
   yumemi_lints: ^3.1.0
diff --git a/test/fixtures/weather_fixtures.dart b/test/fixtures/weather_fixtures.dart
new file mode 100644
index 0000000..47f8fa9
--- /dev/null
+++ b/test/fixtures/weather_fixtures.dart
@@ -0,0 +1,22 @@
+import 'package:flutter_training/domain/weather/constants/weather_condition.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/domain/weather/entities/weather_target_entity.dart';
+
+final defaultDateTime = DateTime(2025, 4);
+
+WeatherTargetEntity createWeatherTarget({
+  String area = 'tokyo',
+  DateTime? date,
+}) => WeatherTargetEntity(area: area, date: date ?? defaultDateTime);
+
+WeatherInfoEntity createWeatherInfo({
+  WeatherCondition? weatherCondition,
+  int? maxTemperature,
+  int? minTemperature,
+  DateTime? date,
+}) => WeatherInfoEntity(
+  weatherCondition: weatherCondition ?? WeatherCondition.sunny,
+  maxTemperature: maxTemperature ?? 25,
+  minTemperature: minTemperature ?? 1,
+  date: date ?? defaultDateTime,
+);
diff --git a/test/unit_tests/constants/weather_condition_test.dart b/test/unit_tests/constants/weather_condition_test.dart
new file mode 100644
index 0000000..56eabac
--- /dev/null
+++ b/test/unit_tests/constants/weather_condition_test.dart
@@ -0,0 +1,44 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/config/gen/assets.gen.dart';
+import 'package:flutter_training/domain/weather/constants/weather_condition.dart';
+
+void main() {
+  // Arrange
+  test('fromString returns expected WeatherCondition', () {
+    // Act
+    final sunny = WeatherCondition.fromString('sunny');
+    final cloudy = WeatherCondition.fromString('cloudy');
+    final rainy = WeatherCondition.fromString('rainy');
+
+    // Assert
+    expect(sunny, WeatherCondition.sunny);
+    expect(cloudy, WeatherCondition.cloudy);
+    expect(rainy, WeatherCondition.rainy);
+  });
+
+  test('fromString throws Exception, if value is invalid', () {
+    // Arrange
+    const invalidString = 'invalid';
+    bool isInvalidWeatherConditionException(Object e) =>
+        e is Exception &&
+        e.toString().contains('Invalid WeatherCondition: $invalidString');
+
+    // Act and Assert
+    expect(
+      () => WeatherCondition.fromString(invalidString),
+      throwsA(predicate(isInvalidWeatherConditionException)),
+    );
+  });
+
+  test('svgPath returns expected svg path', () {
+    // Act
+    final sunny = WeatherCondition.sunny.svgPath;
+    final cloudy = WeatherCondition.cloudy.svgPath;
+    final rainy = WeatherCondition.rainy.svgPath;
+
+    // Assert
+    expect(sunny, Assets.images.sunny);
+    expect(cloudy, Assets.images.cloudy);
+    expect(rainy, Assets.images.rainy);
+  });
+}
diff --git a/test/unit_tests/entities/weather_info_entity_test.dart b/test/unit_tests/entities/weather_info_entity_test.dart
new file mode 100644
index 0000000..712577d
--- /dev/null
+++ b/test/unit_tests/entities/weather_info_entity_test.dart
@@ -0,0 +1,31 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+
+void main() {
+  // Arrange
+  final expectedWeatherInfo = createWeatherInfo();
+  final expectedJson = {
+    'weather_condition': expectedWeatherInfo.weatherCondition.name,
+    'max_temperature': expectedWeatherInfo.maxTemperature,
+    'min_temperature': expectedWeatherInfo.minTemperature,
+    'date': expectedWeatherInfo.date.toIso8601String(),
+  };
+
+  test('toJson returns expected json', () {
+    // Act
+    final json = expectedWeatherInfo.toJson();
+
+    // Assert
+    expect(json, expectedJson);
+  });
+
+  test('fromJson returns expected weather info', () {
+    // Act
+    final weatherInfo = WeatherInfoEntity.fromJson(expectedJson);
+
+    // Assert
+    expect(weatherInfo, expectedWeatherInfo);
+  });
+}
diff --git a/test/unit_tests/entities/weather_target_entity_test.dart b/test/unit_tests/entities/weather_target_entity_test.dart
new file mode 100644
index 0000000..8b23e6f
--- /dev/null
+++ b/test/unit_tests/entities/weather_target_entity_test.dart
@@ -0,0 +1,29 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/domain/weather/entities/weather_target_entity.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+
+void main() {
+  // Arrange
+  final expectedWeatherTarget = createWeatherTarget();
+  final expectedJson = {
+    'area': expectedWeatherTarget.area,
+    'date': expectedWeatherTarget.date.toIso8601String(),
+  };
+
+  test('toJson returns expected json', () {
+    // Act
+    final json = expectedWeatherTarget.toJson();
+
+    // Assert
+    expect(json, expectedJson);
+  });
+
+  test('fromJson returns expected weather target', () {
+    // Act
+    final weatherTarget = WeatherTargetEntity.fromJson(expectedJson);
+
+    // Assert
+    expect(weatherTarget, expectedWeatherTarget);
+  });
+}
diff --git a/test/unit_tests/repositories/weather_repository_test.dart b/test/unit_tests/repositories/weather_repository_test.dart
new file mode 100644
index 0000000..743c4af
--- /dev/null
+++ b/test/unit_tests/repositories/weather_repository_test.dart
@@ -0,0 +1,104 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/constants/weather_error_messages.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/api/weather_service.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/infrastructure/weather/repository_providers.dart';
+import 'package:flutter_training/infrastructure/weather/service_providers.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:yumemi_weather/yumemi_weather.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_repository_test.mocks.dart';
+
+@GenerateNiceMocks([MockSpec<WeatherService>()])
+void main() {
+  // Arrange
+  final mockWeatherService = MockWeatherService();
+  final weatherRepository = WeatherRepository(mockWeatherService);
+  final weatherTarget = createWeatherTarget();
+
+  test('weatherRepositoryProvider creates WeatherRepository', () {
+    // Arrange
+    final container = ProviderContainer(
+      overrides: [weatherServiceProvider.overrideWithValue(mockWeatherService)],
+    );
+
+    // Act
+    final repository = container.read(weatherRepositoryProvider);
+
+    // Assert
+    expect(repository, isA<WeatherRepository>());
+
+    // Clean up
+    container.dispose();
+  });
+
+  test('getWeather returns expected weather info, if API call succeeds', () {
+    // Arrange
+    final expectedWeatherInfo = createWeatherInfo(date: weatherTarget.date);
+    when(
+      mockWeatherService.fetchWeather(weatherTarget),
+    ).thenReturn(expectedWeatherInfo);
+
+    // Act
+    final result = weatherRepository.getWeather(weatherTarget);
+
+    // Assert
+    expect(
+      result,
+      isA<Success<WeatherInfoEntity>>().having(
+        (success) => success.value,
+        'having value equal to expectedWeatherInfo',
+        expectedWeatherInfo,
+      ),
+    );
+    verify(mockWeatherService.fetchWeather(weatherTarget)).called(1);
+  });
+
+  for (final error in YumemiWeatherError.values) {
+    test('getWeather returns Failure, if API throws $error', () {
+      // Arrange
+      when(mockWeatherService.fetchWeather(weatherTarget)).thenThrow(error);
+
+      // Act
+      final result = weatherRepository.getWeather(weatherTarget);
+
+      // Assert
+      expect(
+        result,
+        isA<Failure<WeatherInfoEntity>>().having(
+          (failure) => failure.message,
+          'having message equal to $error',
+          error.message,
+        ),
+      );
+      verify(mockWeatherService.fetchWeather(weatherTarget)).called(1);
+    });
+  }
+
+  test('getWeather returns Failure, if API returns invalid data', () {
+    // Arrange
+    when(
+      mockWeatherService.fetchWeather(weatherTarget),
+    ).thenThrow(const FormatException());
+
+    // Act
+    final result = weatherRepository.getWeather(weatherTarget);
+
+    // Assert
+    expect(
+      result,
+      isA<Failure<WeatherInfoEntity>>().having(
+        (failure) => failure.message,
+        'having message equal to ${CommonErrorMessages.unknown.message}',
+        CommonErrorMessages.unknown.message,
+      ),
+    );
+    verify(mockWeatherService.fetchWeather(weatherTarget)).called(1);
+  });
+}
diff --git a/test/unit_tests/repositories/weather_repository_test.mocks.dart b/test/unit_tests/repositories/weather_repository_test.mocks.dart
new file mode 100644
index 0000000..d8e09b8
--- /dev/null
+++ b/test/unit_tests/repositories/weather_repository_test.mocks.dart
@@ -0,0 +1,52 @@
+// Mocks generated by Mockito 5.4.5 from annotations
+// in flutter_training/test/unit_tests/repositories/weather_repository_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart'
+    as _i2;
+import 'package:flutter_training/domain/weather/entities/weather_target_entity.dart'
+    as _i4;
+import 'package:flutter_training/infrastructure/weather/api/weather_service.dart'
+    as _i3;
+import 'package:mockito/mockito.dart' as _i1;
+
+// 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: must_be_immutable
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+
+class _FakeWeatherInfoEntity_0 extends _i1.SmartFake
+    implements _i2.WeatherInfoEntity {
+  _FakeWeatherInfoEntity_0(Object parent, Invocation parentInvocation)
+    : super(parent, parentInvocation);
+}
+
+/// A class which mocks [WeatherService].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockWeatherService extends _i1.Mock implements _i3.WeatherService {
+  @override
+  _i2.WeatherInfoEntity fetchWeather(_i4.WeatherTargetEntity? weatherTarget) =>
+      (super.noSuchMethod(
+            Invocation.method(#fetchWeather, [weatherTarget]),
+            returnValue: _FakeWeatherInfoEntity_0(
+              this,
+              Invocation.method(#fetchWeather, [weatherTarget]),
+            ),
+            returnValueForMissingStub: _FakeWeatherInfoEntity_0(
+              this,
+              Invocation.method(#fetchWeather, [weatherTarget]),
+            ),
+          )
+          as _i2.WeatherInfoEntity);
+}
diff --git a/test/unit_tests/services/weather_service_test.dart b/test/unit_tests/services/weather_service_test.dart
new file mode 100644
index 0000000..c518408
--- /dev/null
+++ b/test/unit_tests/services/weather_service_test.dart
@@ -0,0 +1,77 @@
+import 'dart:convert';
+
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/infrastructure/weather/api/weather_service.dart';
+import 'package:flutter_training/infrastructure/weather/service_providers.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:yumemi_weather/yumemi_weather.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_service_test.mocks.dart';
+
+@GenerateNiceMocks([MockSpec<YumemiWeather>()])
+void main() {
+  // Arrange
+  final mockYumemiWeather = MockYumemiWeather();
+  final weatherService = WeatherService(mockYumemiWeather);
+  final weatherTarget = createWeatherTarget();
+  final weatherTargetJson = jsonEncode(weatherTarget.toJson());
+
+  test('weatherServiceProvider creates WeatherService', () {
+    // Arrange
+    final container = ProviderContainer();
+
+    // Act
+    final service = container.read(weatherServiceProvider);
+
+    // Assert
+    expect(service, isA<WeatherService>());
+
+    // Clean up
+    container.dispose();
+  });
+
+  test('fetchWeather returns expected weather info, if API call succeeds', () {
+    // Arrange
+    final expectedWeatherInfo = createWeatherInfo(date: weatherTarget.date);
+    final expectedJson = jsonEncode(expectedWeatherInfo.toJson());
+    when(
+      mockYumemiWeather.fetchWeather(weatherTargetJson),
+    ).thenReturn(expectedJson);
+
+    // Act
+    final weatherInfo = weatherService.fetchWeather(weatherTarget);
+
+    // Assert
+    expect(weatherInfo, expectedWeatherInfo);
+    verify(mockYumemiWeather.fetchWeather(weatherTargetJson)).called(1);
+  });
+
+  for (final error in YumemiWeatherError.values) {
+    test('fetchWeather throws $error, if API call fails', () {
+      // Arrange
+      when(mockYumemiWeather.fetchWeather(weatherTargetJson)).thenThrow(error);
+
+      // Act and Assert
+      expect(() => weatherService.fetchWeather(weatherTarget), throwsA(error));
+      verify(mockYumemiWeather.fetchWeather(weatherTargetJson)).called(1);
+    });
+  }
+
+  test('fetchWeather throws FormatException, if API returns invalid data', () {
+    // Arrange
+    const invalidJson = 'invalid json';
+    when(
+      mockYumemiWeather.fetchWeather(weatherTargetJson),
+    ).thenReturn(invalidJson);
+
+    // Act and Assert
+    expect(
+      () => weatherService.fetchWeather(weatherTarget),
+      throwsA(isA<FormatException>()),
+    );
+    verify(mockYumemiWeather.fetchWeather(weatherTargetJson)).called(1);
+  });
+}
diff --git a/test/unit_tests/services/weather_service_test.mocks.dart b/test/unit_tests/services/weather_service_test.mocks.dart
new file mode 100644
index 0000000..7536032
--- /dev/null
+++ b/test/unit_tests/services/weather_service_test.mocks.dart
@@ -0,0 +1,87 @@
+// Mocks generated by Mockito 5.4.5 from annotations
+// in flutter_training/test/unit_tests/services/weather_service_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:mockito/src/dummies.dart' as _i3;
+import 'package:yumemi_weather/src/yumemi_weather_base.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: must_be_immutable
+// 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 [YumemiWeather].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockYumemiWeather extends _i1.Mock implements _i2.YumemiWeather {
+  @override
+  String fetchSimpleWeather() =>
+      (super.noSuchMethod(
+            Invocation.method(#fetchSimpleWeather, []),
+            returnValue: _i3.dummyValue<String>(
+              this,
+              Invocation.method(#fetchSimpleWeather, []),
+            ),
+            returnValueForMissingStub: _i3.dummyValue<String>(
+              this,
+              Invocation.method(#fetchSimpleWeather, []),
+            ),
+          )
+          as String);
+
+  @override
+  String fetchThrowsWeather(String? area) =>
+      (super.noSuchMethod(
+            Invocation.method(#fetchThrowsWeather, [area]),
+            returnValue: _i3.dummyValue<String>(
+              this,
+              Invocation.method(#fetchThrowsWeather, [area]),
+            ),
+            returnValueForMissingStub: _i3.dummyValue<String>(
+              this,
+              Invocation.method(#fetchThrowsWeather, [area]),
+            ),
+          )
+          as String);
+
+  @override
+  String fetchWeather(String? jsonString) =>
+      (super.noSuchMethod(
+            Invocation.method(#fetchWeather, [jsonString]),
+            returnValue: _i3.dummyValue<String>(
+              this,
+              Invocation.method(#fetchWeather, [jsonString]),
+            ),
+            returnValueForMissingStub: _i3.dummyValue<String>(
+              this,
+              Invocation.method(#fetchWeather, [jsonString]),
+            ),
+          )
+          as String);
+
+  @override
+  String syncFetchWeather(String? jsonString) =>
+      (super.noSuchMethod(
+            Invocation.method(#syncFetchWeather, [jsonString]),
+            returnValue: _i3.dummyValue<String>(
+              this,
+              Invocation.method(#syncFetchWeather, [jsonString]),
+            ),
+            returnValueForMissingStub: _i3.dummyValue<String>(
+              this,
+              Invocation.method(#syncFetchWeather, [jsonString]),
+            ),
+          )
+          as String);
+}
diff --git a/test/unit_tests/usecases/reload_weather_usecase_test.dart b/test/unit_tests/usecases/reload_weather_usecase_test.dart
new file mode 100644
index 0000000..b834961
--- /dev/null
+++ b/test/unit_tests/usecases/reload_weather_usecase_test.dart
@@ -0,0 +1,114 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/application/weather/usecase_providers.dart';
+import 'package:flutter_training/application/weather/usecases/reload_weather_usecase.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/constants/weather_error_messages.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/infrastructure/weather/repository_providers.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:yumemi_weather/yumemi_weather.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'reload_weather_usecase_test.mocks.dart';
+
+@GenerateNiceMocks([MockSpec<WeatherRepository>(), MockSpec<YumemiWeather>()])
+void main() {
+  // Arrange
+  final mockWeatherRepository = MockWeatherRepository();
+  final reloadWeatherUseCase = ReloadWeatherUseCase(mockWeatherRepository);
+  final weatherTarget = createWeatherTarget();
+
+  test('reloadWeatherUseCaseProvider creates ReloadWeatherUseCase', () {
+    // Arrange
+    final container = ProviderContainer(
+      overrides: [
+        weatherRepositoryProvider.overrideWithValue(mockWeatherRepository),
+      ],
+    );
+
+    // Act
+    final useCase = container.read(reloadWeatherUseCaseProvider);
+
+    // Assert
+    expect(useCase, isA<ReloadWeatherUseCase>());
+
+    // Clean up
+    container.dispose();
+  });
+
+  test('call returns expected weather info, if API call succeeds', () {
+    // Arrange
+    final expectedWeatherInfo = createWeatherInfo(date: weatherTarget.date);
+    final expectedResult = Success(expectedWeatherInfo);
+    provideDummy<Result<WeatherInfoEntity>>(expectedResult);
+    when(
+      mockWeatherRepository.getWeather(weatherTarget),
+    ).thenReturn(expectedResult);
+
+    // Act
+    final result = reloadWeatherUseCase.call(weatherTarget);
+
+    // Assert
+    expect(
+      result,
+      isA<Success<WeatherInfoEntity>>().having(
+        (success) => success.value,
+        'having value equal to expectedWeatherInfo',
+        expectedWeatherInfo,
+      ),
+    );
+    verify(mockWeatherRepository.getWeather(weatherTarget)).called(1);
+  });
+
+  for (final error in YumemiWeatherError.values) {
+    test('call returns Failure, if API throws $error', () {
+      // Arrange
+      final expectedResult = Failure<WeatherInfoEntity>(error.message);
+      when(
+        mockWeatherRepository.getWeather(weatherTarget),
+      ).thenReturn(expectedResult);
+
+      // Act
+      final result = reloadWeatherUseCase.call(weatherTarget);
+
+      // Assert
+      expect(
+        result,
+        isA<Failure<WeatherInfoEntity>>().having(
+          (failure) => failure.message,
+          'having message equal to ${error.message}',
+          error.message,
+        ),
+      );
+      verify(mockWeatherRepository.getWeather(weatherTarget)).called(1);
+    });
+  }
+
+  test('call returns Failure, if API returns invalid data', () {
+    // Arrange
+    final expectedResult = Failure<WeatherInfoEntity>(
+      CommonErrorMessages.unknown.message,
+    );
+    when(
+      mockWeatherRepository.getWeather(weatherTarget),
+    ).thenReturn(expectedResult);
+
+    // Act
+    final result = reloadWeatherUseCase.call(weatherTarget);
+
+    // Assert
+    expect(
+      result,
+      isA<Failure<WeatherInfoEntity>>().having(
+        (failure) => failure.message,
+        'having message equal to ${CommonErrorMessages.unknown.message}',
+        CommonErrorMessages.unknown.message,
+      ),
+    );
+    verify(mockWeatherRepository.getWeather(weatherTarget)).called(1);
+  });
+}
diff --git a/test/unit_tests/usecases/reload_weather_usecase_test.mocks.dart b/test/unit_tests/usecases/reload_weather_usecase_test.mocks.dart
new file mode 100644
index 0000000..3ec338c
--- /dev/null
+++ b/test/unit_tests/usecases/reload_weather_usecase_test.mocks.dart
@@ -0,0 +1,117 @@
+// Mocks generated by Mockito 5.4.5 from annotations
+// in flutter_training/test/unit_tests/usecases/reload_weather_usecase_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'package:flutter_training/domain/common/interfaces/result.dart' as _i3;
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart'
+    as _i4;
+import 'package:flutter_training/domain/weather/entities/weather_target_entity.dart'
+    as _i5;
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart'
+    as _i2;
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:mockito/src/dummies.dart' as _i6;
+import 'package:yumemi_weather/yumemi_weather.dart' as _i7;
+
+// 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: must_be_immutable
+// 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 [WeatherRepository].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockWeatherRepository extends _i1.Mock implements _i2.WeatherRepository {
+  @override
+  _i3.Result<_i4.WeatherInfoEntity> getWeather(
+    _i5.WeatherTargetEntity? weatherTarget,
+  ) =>
+      (super.noSuchMethod(
+            Invocation.method(#getWeather, [weatherTarget]),
+            returnValue: _i6.dummyValue<_i3.Result<_i4.WeatherInfoEntity>>(
+              this,
+              Invocation.method(#getWeather, [weatherTarget]),
+            ),
+            returnValueForMissingStub: _i6
+                .dummyValue<_i3.Result<_i4.WeatherInfoEntity>>(
+                  this,
+                  Invocation.method(#getWeather, [weatherTarget]),
+                ),
+          )
+          as _i3.Result<_i4.WeatherInfoEntity>);
+}
+
+/// A class which mocks [YumemiWeather].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockYumemiWeather extends _i1.Mock implements _i7.YumemiWeather {
+  @override
+  String fetchSimpleWeather() =>
+      (super.noSuchMethod(
+            Invocation.method(#fetchSimpleWeather, []),
+            returnValue: _i6.dummyValue<String>(
+              this,
+              Invocation.method(#fetchSimpleWeather, []),
+            ),
+            returnValueForMissingStub: _i6.dummyValue<String>(
+              this,
+              Invocation.method(#fetchSimpleWeather, []),
+            ),
+          )
+          as String);
+
+  @override
+  String fetchThrowsWeather(String? area) =>
+      (super.noSuchMethod(
+            Invocation.method(#fetchThrowsWeather, [area]),
+            returnValue: _i6.dummyValue<String>(
+              this,
+              Invocation.method(#fetchThrowsWeather, [area]),
+            ),
+            returnValueForMissingStub: _i6.dummyValue<String>(
+              this,
+              Invocation.method(#fetchThrowsWeather, [area]),
+            ),
+          )
+          as String);
+
+  @override
+  String fetchWeather(String? jsonString) =>
+      (super.noSuchMethod(
+            Invocation.method(#fetchWeather, [jsonString]),
+            returnValue: _i6.dummyValue<String>(
+              this,
+              Invocation.method(#fetchWeather, [jsonString]),
+            ),
+            returnValueForMissingStub: _i6.dummyValue<String>(
+              this,
+              Invocation.method(#fetchWeather, [jsonString]),
+            ),
+          )
+          as String);
+
+  @override
+  String syncFetchWeather(String? jsonString) =>
+      (super.noSuchMethod(
+            Invocation.method(#syncFetchWeather, [jsonString]),
+            returnValue: _i6.dummyValue<String>(
+              this,
+              Invocation.method(#syncFetchWeather, [jsonString]),
+            ),
+            returnValueForMissingStub: _i6.dummyValue<String>(
+              this,
+              Invocation.method(#syncFetchWeather, [jsonString]),
+            ),
+          )
+          as String);
+}
diff --git a/test/unit_tests/view_models/weather_view_model_test.dart b/test/unit_tests/view_models/weather_view_model_test.dart
new file mode 100644
index 0000000..bb9d257
--- /dev/null
+++ b/test/unit_tests/view_models/weather_view_model_test.dart
@@ -0,0 +1,137 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/application/weather/usecase_providers.dart';
+import 'package:flutter_training/application/weather/usecases/reload_weather_usecase.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/presentation/weather/view_models/weather_view_model.dart';
+import 'package:flutter_training/presentation/weather/view_states/weather_view_state.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_view_model_test.mocks.dart';
+
+@GenerateNiceMocks([MockSpec<WeatherRepository>()])
+void main() {
+  // Arrange
+  late MockWeatherRepository mockWeatherRepository;
+  late ReloadWeatherUseCase reloadWeatherUseCase;
+  late ProviderContainer container;
+
+  final expectedWeatherTarget = createWeatherTarget();
+  final expectedWeatherInfo = createWeatherInfo(
+    date: expectedWeatherTarget.date, // 固定された日付を使用
+  );
+  final expectedErrorMessage = CommonErrorMessages.unknown.message;
+  final expectedSuccessResult = Success(expectedWeatherInfo);
+  final expectedFailureResult = Failure<WeatherInfoEntity>(
+    expectedErrorMessage,
+  );
+
+  setUp(() {
+    mockWeatherRepository = MockWeatherRepository();
+    reloadWeatherUseCase = ReloadWeatherUseCase(mockWeatherRepository);
+    container = ProviderContainer(
+      overrides: [
+        reloadWeatherUseCaseProvider.overrideWithValue(reloadWeatherUseCase),
+      ],
+    );
+  });
+
+  tearDown(() {
+    container.dispose();
+  });
+
+  test('weatherViewModelProvider returns WeatherViewState when read', () {
+    // Act
+    final state = container.read(weatherViewModelProvider);
+
+    // Assert
+    expect(state, const WeatherViewState());
+  });
+
+  test('reloadWeather updates state successfully', () {
+    // Arrange
+    final viewModel = container.read(weatherViewModelProvider.notifier);
+    provideDummy<Result<WeatherInfoEntity>>(expectedSuccessResult);
+    when(
+      mockWeatherRepository.getWeather(expectedWeatherTarget),
+    ).thenReturn(expectedSuccessResult);
+
+    // Act
+    viewModel.reloadWeather(expectedWeatherTarget);
+
+    // Assert
+    expect(viewModel.state.errorMessage, null);
+    expect(
+      viewModel.state.weatherCondition,
+      expectedWeatherInfo.weatherCondition,
+    );
+    expect(
+      viewModel.state.minTemperature,
+      expectedWeatherInfo.minTemperature.toString(),
+    );
+    expect(
+      viewModel.state.maxTemperature,
+      expectedWeatherInfo.maxTemperature.toString(),
+    );
+    verify(mockWeatherRepository.getWeather(expectedWeatherTarget)).called(1);
+  });
+
+  test('reloadWeather handles error successfully, '
+      'and the forecast remains a placeholder', () {
+    // Arrange
+    final viewModel = container.read(weatherViewModelProvider.notifier);
+    provideDummy<Result<WeatherInfoEntity>>(expectedFailureResult);
+    when(
+      mockWeatherRepository.getWeather(expectedWeatherTarget),
+    ).thenReturn(expectedFailureResult);
+
+    // Act
+    viewModel.reloadWeather(expectedWeatherTarget);
+
+    // Assert
+    const placeholderTemperature = '**';
+    expect(viewModel.state.errorMessage, expectedErrorMessage);
+    expect(viewModel.state.weatherCondition, null);
+    expect(viewModel.state.minTemperature, placeholderTemperature);
+    expect(viewModel.state.maxTemperature, placeholderTemperature);
+    verify(mockWeatherRepository.getWeather(expectedWeatherTarget)).called(1);
+  });
+
+  test('reloadWeather handles error successfully, after state is updated', () {
+    // Arrange
+    final viewModel = container.read(weatherViewModelProvider.notifier);
+    viewModel.state = WeatherViewState(
+      weatherCondition: expectedWeatherInfo.weatherCondition,
+      minTemperature: expectedWeatherInfo.minTemperature.toString(),
+      maxTemperature: expectedWeatherInfo.maxTemperature.toString(),
+    );
+    provideDummy<Result<WeatherInfoEntity>>(expectedFailureResult);
+    when(
+      mockWeatherRepository.getWeather(expectedWeatherTarget),
+    ).thenReturn(expectedFailureResult);
+
+    // Act
+    viewModel.reloadWeather(expectedWeatherTarget);
+
+    // Assert
+    expect(viewModel.state.errorMessage, expectedErrorMessage);
+    expect(
+      viewModel.state.weatherCondition,
+      expectedWeatherInfo.weatherCondition,
+    );
+    expect(
+      viewModel.state.minTemperature,
+      expectedWeatherInfo.minTemperature.toString(),
+    );
+    expect(
+      viewModel.state.maxTemperature,
+      expectedWeatherInfo.maxTemperature.toString(),
+    );
+    verify(mockWeatherRepository.getWeather(expectedWeatherTarget)).called(1);
+  });
+}
diff --git a/test/unit_tests/view_models/weather_view_model_test.mocks.dart b/test/unit_tests/view_models/weather_view_model_test.mocks.dart
new file mode 100644
index 0000000..3829b37
--- /dev/null
+++ b/test/unit_tests/view_models/weather_view_model_test.mocks.dart
@@ -0,0 +1,51 @@
+// Mocks generated by Mockito 5.4.5 from annotations
+// in flutter_training/test/unit_tests/view_models/weather_view_model_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'package:flutter_training/domain/common/interfaces/result.dart' as _i3;
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart'
+    as _i4;
+import 'package:flutter_training/domain/weather/entities/weather_target_entity.dart'
+    as _i5;
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart'
+    as _i2;
+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: must_be_immutable
+// 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 [WeatherRepository].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockWeatherRepository extends _i1.Mock implements _i2.WeatherRepository {
+  @override
+  _i3.Result<_i4.WeatherInfoEntity> getWeather(
+    _i5.WeatherTargetEntity? weatherTarget,
+  ) =>
+      (super.noSuchMethod(
+            Invocation.method(#getWeather, [weatherTarget]),
+            returnValue: _i6.dummyValue<_i3.Result<_i4.WeatherInfoEntity>>(
+              this,
+              Invocation.method(#getWeather, [weatherTarget]),
+            ),
+            returnValueForMissingStub: _i6
+                .dummyValue<_i3.Result<_i4.WeatherInfoEntity>>(
+                  this,
+                  Invocation.method(#getWeather, [weatherTarget]),
+                ),
+          )
+          as _i3.Result<_i4.WeatherInfoEntity>);
+}


Reviews:
user: login: trm11tkr
body: いくつかコメントさせていただきましたのでご確認よろしくお願いします！
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T01:01:45Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-23T05:58:34Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-23T06:25:38Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-23T07:19:05Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-23T07:38:12Z
user: login: trm11tkr
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T07:40:57Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-23T07:42:15Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-23T07:45:44Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-23T07:49:59Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-23T07:53:09Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-23T07:56:38Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-23T08:14:22Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-23T08:15:03Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-23T08:17:45Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-23T08:18:55Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-23T08:29:07Z
user: login: trm11tkr
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T02:26:43Z
user: login: trm11tkr
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T02:26:49Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-24T05:58:48Z
user: login: hidekitux
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-24T05:59:47Z
user: login: trm11tkr
body: LGTM です！
ご対応いただきありがとうございます！
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-24T06:14:06Z

Review comments:
diff_hunk: @@ -0,0 +1,38 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/config/gen/assets.gen.dart';
+import 'package:flutter_training/domain/weather/constants/weather_condition.dart';
+
+void main() {
+  // Arrange
+  test('fromString returns expected WeatherCondition', () {
+    // Act
+    final sunny = WeatherCondition.fromString('sunny');
+    final cloudy = WeatherCondition.fromString('cloudy');
+    final rainy = WeatherCondition.fromString('rainy');
+
+    // Assert
+    expect(sunny, WeatherCondition.sunny);
+    expect(cloudy, WeatherCondition.cloudy);
+    expect(rainy, WeatherCondition.rainy);
+  });
+
+  test('fromString throws Exception, if value is invalid', () {
+    // Act and Assert
+    expect(
+      () => WeatherCondition.fromString('invalid'),
+      throwsA(isA<Exception>()),
+    );
+  });
path: test/unit_tests/constants/weather_condition_test.dart
user: login: trm11tkr
body: ![nits-badge](https://img.shields.io/badge/review-nits-green.svg) 
[predicate](https://api.flutter.dev/flutter/package-matcher_matcher/predicate.html) を利用することで Exception の中身まで検証することもできます！

```suggestion
  test('fromString throws Exception, if value is invalid', () {
    // Arrange
    const invalidString = 'invalid';
    bool isInvalidWeatherConditionException(Object e) =>
        e is Exception &&
        e.toString().contains('Invalid WeatherCondition: $invalidString');

    // Act and Assert
    expect(
      () => WeatherCondition.fromString(invalidString),
      throwsA(predicate(isInvalidWeatherConditionException)),
    );
  });
```
created_at: 2025-04-22T06:16:02Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,158 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/application/weather/usecase_providers.dart';
+import 'package:flutter_training/application/weather/usecases/reload_weather_usecase.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/presentation/weather/view_models/weather_view_model.dart';
+import 'package:flutter_training/presentation/weather/view_states/weather_view_state.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_view_model_test.mocks.dart';
+
+/// TODO: verifyでgetWeather(expectedWeatherTarget)がエラーになるのを解決する
path: test/unit_tests/view_models/weather_view_model_test.dart
user: login: trm11tkr
body: ![imo-badge](https://img.shields.io/badge/review-imo-orange.svg) 
ペア作業中にもお話ししましたが改めてコメントさせていただきます！

reloadWeather() 内で DateTime.now() を直接指定しているため、テスト時に WeatherTargetEntity の引数である date を厳密に検証するのが難しいということでよね。

個人的には 引数として任意の DateTime を受け取るように変更し、テストでは任意の日時で検証できるようにすることでテストの柔軟性が上がって良いかと思いました。
created_at: 2025-04-22T07:14:01Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,31 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+
+void main() {
+  // Arrange
+  final expectedWeatherInfo = createWeatherInfo(date: null);
+  final expectedJson = {
+    'weather_condition': expectedWeatherInfo.weatherCondition.name,
+    'max_temperature': expectedWeatherInfo.maxTemperature,
+    'min_temperature': expectedWeatherInfo.minTemperature,
+    'date': expectedWeatherInfo.date.toIso8601String(),
+  };
+
+  test('toJson returns expected json', () {
+    // Act
+    final json = expectedWeatherInfo.toJson();
+
+    // Assert
+    expect(json, expectedJson);
+  });
+
+  test('fromJson returns expected weather info', () {
+    // Act
+    final weatherInfo = WeatherInfoEntity.fromJson(expectedJson);
+
+    // Assert
+    expect(weatherInfo, equals(expectedWeatherInfo));
path: test/unit_tests/entities/weather_info_entity_test.dart
user: login: trm11tkr
body: > /// [matcher] can be a value in which case it will be wrapped in an
/// [equals] matcher.

expect method の説明にこのようにあるので、equals は利用しなくても内部的にラップしてくれそうです。

![ask-badge](https://img.shields.io/badge/review-ask-yellowgreen.svg) 
こちらは意図的に利用されていますか？
created_at: 2025-04-22T07:27:11Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,22 @@
+import 'package:flutter_training/domain/weather/constants/weather_condition.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/domain/weather/entities/weather_target_entity.dart';
+
+WeatherTargetEntity createWeatherTarget({
+  String area = 'tokyo',
+  DateTime? date,
+}) => WeatherTargetEntity(area: area, date: date ?? DateTime(2025, 4));
+
+/// createWeatherTargetを利用するテストにおいて、そのdateや他の値をdateに明示的に指定する
+/// createWeatherTargetを利用しないテストにおいて、nullや他の値をdateに明示的に指定する
+WeatherInfoEntity createWeatherInfo({
+  required DateTime? date,
path: test/fixtures/weather_fixtures.dart
user: login: trm11tkr
body: ![ask-badge](https://img.shields.io/badge/review-ask-yellowgreen.svg) 
呼び出し側で必ず date を必ず指定させるためにこのように実装していただいたと理解しましたが、そのように対応した背景を教えていただけますでしょうか？🙏

`weather_info_entity_test.dart` では createWeatherInfo に null を指定しているのに、 expectedJson では date に toIso8601String() で変換処理していたりして若干違和感があるかもしれないと思いました。
```dart
  final expectedWeatherInfo = createWeatherInfo(date: null);
  final expectedJson = {
    'weather_condition': expectedWeatherInfo.weatherCondition.name,
    'max_temperature': expectedWeatherInfo.maxTemperature,
    'min_temperature': expectedWeatherInfo.minTemperature,
    'date': expectedWeatherInfo.date.toIso8601String(),
  };

```
created_at: 2025-04-22T07:34:05Z
author_association: COLLABORATOR
path: test/unit_tests/repositories/weather_repository_test.dart
user: login: trm11tkr
body: ![nits-badge](https://img.shields.io/badge/review-nits-green.svg) 
any を指定しているところで weatherTarget を指定するとより正確に検証できそうです。
created_at: 2025-04-22T07:55:37Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/constants/weather_error_messages.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/api/weather_service.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/infrastructure/weather/repository_providers.dart';
+import 'package:flutter_training/infrastructure/weather/service_providers.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:yumemi_weather/yumemi_weather.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_repository_test.mocks.dart';
+
+@GenerateNiceMocks([MockSpec<WeatherService>()])
+void main() {
+  // Arrange
+  final mockWeatherService = MockWeatherService();
+  final weatherRepository = WeatherRepository(mockWeatherService);
+  final weatherTarget = createWeatherTarget();
+
+  test('weatherRepositoryProvider creates WeatherRepository', () {
+    // Arrange
+    final container = ProviderContainer(
+      overrides: [weatherServiceProvider.overrideWithValue(mockWeatherService)],
+    );
+
+    // Act
+    final repository = container.read(weatherRepositoryProvider);
+
+    // Assert
+    expect(repository, isA<WeatherRepository>());
+
+    // Clean up
+    container.dispose();
+  });
+
+  test('getWeather returns expected weather info, if API call succeeds', () {
+    // Arrange
+    final expectedWeatherInfo = createWeatherInfo(date: weatherTarget.date);
+    when(mockWeatherService.fetchWeather(any)).thenReturn(expectedWeatherInfo);
+
+    // Act
+    final result = weatherRepository.getWeather(weatherTarget);
+
+    // Assert
+    expect(result, isA<Success<WeatherInfoEntity>>());
+    final weatherInfo = (result as Success<WeatherInfoEntity>).value;
+    expect(weatherInfo, equals(expectedWeatherInfo));
path: test/unit_tests/repositories/weather_repository_test.dart
user: login: trm11tkr
body: [renview-info]
↓のようにまとめてチェックすることもできます🙆‍♂️
```suggestion
    expect(
    result,
    isA<Success<WeatherInfoEntity>>().having(
      (result) => result.value,
      'having value equal to expectedWeatherInfo',
      expectedWeatherInfo,
    ),
  );
```
created_at: 2025-04-22T08:04:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/constants/weather_error_messages.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/api/weather_service.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/infrastructure/weather/repository_providers.dart';
+import 'package:flutter_training/infrastructure/weather/service_providers.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:yumemi_weather/yumemi_weather.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_repository_test.mocks.dart';
+
+@GenerateNiceMocks([MockSpec<WeatherService>()])
+void main() {
+  // Arrange
+  final mockWeatherService = MockWeatherService();
+  final weatherRepository = WeatherRepository(mockWeatherService);
+  final weatherTarget = createWeatherTarget();
+
+  test('weatherRepositoryProvider creates WeatherRepository', () {
+    // Arrange
+    final container = ProviderContainer(
+      overrides: [weatherServiceProvider.overrideWithValue(mockWeatherService)],
+    );
+
+    // Act
+    final repository = container.read(weatherRepositoryProvider);
+
+    // Assert
+    expect(repository, isA<WeatherRepository>());
+
+    // Clean up
+    container.dispose();
+  });
+
+  test('getWeather returns expected weather info, if API call succeeds', () {
+    // Arrange
+    final expectedWeatherInfo = createWeatherInfo(date: weatherTarget.date);
+    when(mockWeatherService.fetchWeather(any)).thenReturn(expectedWeatherInfo);
+
+    // Act
+    final result = weatherRepository.getWeather(weatherTarget);
+
+    // Assert
+    expect(result, isA<Success<WeatherInfoEntity>>());
+    final weatherInfo = (result as Success<WeatherInfoEntity>).value;
+    expect(weatherInfo, equals(expectedWeatherInfo));
+    verify(mockWeatherService.fetchWeather(weatherTarget)).called(1);
+  });
+
+  for (final error in YumemiWeatherError.values) {
+    test('getWeather returns Failure, if API throws $error', () {
+      // Arrange
+      late Result<WeatherInfoEntity> result;
+      when(mockWeatherService.fetchWeather(any)).thenThrow(error);
+
+      // Act and Assert
+      expect(
+        () => result = weatherRepository.getWeather(weatherTarget),
+        returnsNormally,
+      );
+      expect(
+        result,
+        isA<Failure<WeatherInfoEntity>>().having(
+          (e) => (e as Failure).message,
path: test/unit_tests/repositories/weather_repository_test.dart
user: login: trm11tkr
body: ![ask-badge](https://img.shields.io/badge/review-ask-yellowgreen.svg) 
ここで型キャストしている背景を教えていただきたいです！
having を使用しているので不要な認識です。
created_at: 2025-04-22T08:21:11Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/constants/weather_error_messages.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/api/weather_service.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/infrastructure/weather/repository_providers.dart';
+import 'package:flutter_training/infrastructure/weather/service_providers.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:yumemi_weather/yumemi_weather.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_repository_test.mocks.dart';
+
+@GenerateNiceMocks([MockSpec<WeatherService>()])
+void main() {
+  // Arrange
+  final mockWeatherService = MockWeatherService();
+  final weatherRepository = WeatherRepository(mockWeatherService);
+  final weatherTarget = createWeatherTarget();
+
+  test('weatherRepositoryProvider creates WeatherRepository', () {
+    // Arrange
+    final container = ProviderContainer(
+      overrides: [weatherServiceProvider.overrideWithValue(mockWeatherService)],
+    );
+
+    // Act
+    final repository = container.read(weatherRepositoryProvider);
+
+    // Assert
+    expect(repository, isA<WeatherRepository>());
+
+    // Clean up
+    container.dispose();
+  });
+
+  test('getWeather returns expected weather info, if API call succeeds', () {
+    // Arrange
+    final expectedWeatherInfo = createWeatherInfo(date: weatherTarget.date);
+    when(mockWeatherService.fetchWeather(any)).thenReturn(expectedWeatherInfo);
+
+    // Act
+    final result = weatherRepository.getWeather(weatherTarget);
+
+    // Assert
+    expect(result, isA<Success<WeatherInfoEntity>>());
+    final weatherInfo = (result as Success<WeatherInfoEntity>).value;
+    expect(weatherInfo, equals(expectedWeatherInfo));
+    verify(mockWeatherService.fetchWeather(weatherTarget)).called(1);
+  });
+
+  for (final error in YumemiWeatherError.values) {
+    test('getWeather returns Failure, if API throws $error', () {
+      // Arrange
+      late Result<WeatherInfoEntity> result;
+      when(mockWeatherService.fetchWeather(any)).thenThrow(error);
+
+      // Act and Assert
+      expect(
+        () => result = weatherRepository.getWeather(weatherTarget),
+        returnsNormally,
+      );
path: test/unit_tests/repositories/weather_repository_test.dart
user: login: trm11tkr
body: ![ask-badge](https://img.shields.io/badge/review-ask-yellowgreen.svg) 
returnsNormally を検証している背景を教えていただきたいです！

### メモ
- getWeather() が例外を投げる設計ではない（内部で try-catch 済み）
- 直後に expect(result, isA<Failure>()) をしており、もし throw されたらそこでテスト失敗になる
- expect(() => result = ..., returnsNormally) は少し冗長になるかもしれない
created_at: 2025-04-22T08:42:30Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,158 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/application/weather/usecase_providers.dart';
+import 'package:flutter_training/application/weather/usecases/reload_weather_usecase.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/presentation/weather/view_models/weather_view_model.dart';
+import 'package:flutter_training/presentation/weather/view_states/weather_view_state.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_view_model_test.mocks.dart';
+
+/// TODO: verifyでgetWeather(expectedWeatherTarget)がエラーになるのを解決する
+/// WeatherViewModel内部でDateTime.now()が使われており、時間を変更するとテストが失敗する
+@GenerateNiceMocks([MockSpec<WeatherRepository>()])
+void main() {
+  // Arrange
+  late MockWeatherRepository mockWeatherRepository;
+  late ReloadWeatherUseCase reloadWeatherUseCase;
+  late ProviderContainer container;
+
+  final expectedWeatherTarget = createWeatherTarget();
+  final expectedWeatherInfo = createWeatherInfo(
+    date: expectedWeatherTarget.date, // 固定された日付を使用
+  );
+  final expectedErrorMessage = CommonErrorMessages.unknown.message;
+  final expectedSuccessResult = Success(expectedWeatherInfo);
+  final expectedFailureResult = Failure<WeatherInfoEntity>(
+    expectedErrorMessage,
+  );
+  const placeholderTemperature = '**';
+
+  setUp(() {
+    mockWeatherRepository = MockWeatherRepository();
+    reloadWeatherUseCase = ReloadWeatherUseCase(mockWeatherRepository);
+    container = ProviderContainer(
+      overrides: [
+        reloadWeatherUseCaseProvider.overrideWithValue(reloadWeatherUseCase),
+      ],
+    );
+  });
+
+  tearDown(() {
+    container.dispose();
+  });
+
+  test('weatherViewModelProvider returns WeatherViewState when read', () {
+    // Act
+    final state = container.read(weatherViewModelProvider);
+
+    // Assert
+    expect(state, isA<WeatherViewState>());
path: test/unit_tests/view_models/weather_view_model_test.dart
user: login: trm11tkr
body: ![nits-badge](https://img.shields.io/badge/review-nits-green.svg) 
having で中身も比較してあげると丁寧かと思いました。
created_at: 2025-04-22T08:56:39Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,158 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/application/weather/usecase_providers.dart';
+import 'package:flutter_training/application/weather/usecases/reload_weather_usecase.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/presentation/weather/view_models/weather_view_model.dart';
+import 'package:flutter_training/presentation/weather/view_states/weather_view_state.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_view_model_test.mocks.dart';
+
+/// TODO: verifyでgetWeather(expectedWeatherTarget)がエラーになるのを解決する
+/// WeatherViewModel内部でDateTime.now()が使われており、時間を変更するとテストが失敗する
+@GenerateNiceMocks([MockSpec<WeatherRepository>()])
+void main() {
+  // Arrange
+  late MockWeatherRepository mockWeatherRepository;
+  late ReloadWeatherUseCase reloadWeatherUseCase;
+  late ProviderContainer container;
+
+  final expectedWeatherTarget = createWeatherTarget();
+  final expectedWeatherInfo = createWeatherInfo(
+    date: expectedWeatherTarget.date, // 固定された日付を使用
+  );
+  final expectedErrorMessage = CommonErrorMessages.unknown.message;
+  final expectedSuccessResult = Success(expectedWeatherInfo);
+  final expectedFailureResult = Failure<WeatherInfoEntity>(
+    expectedErrorMessage,
+  );
+  const placeholderTemperature = '**';
+
+  setUp(() {
+    mockWeatherRepository = MockWeatherRepository();
+    reloadWeatherUseCase = ReloadWeatherUseCase(mockWeatherRepository);
+    container = ProviderContainer(
+      overrides: [
+        reloadWeatherUseCaseProvider.overrideWithValue(reloadWeatherUseCase),
+      ],
+    );
+  });
+
+  tearDown(() {
+    container.dispose();
+  });
+
+  test('weatherViewModelProvider returns WeatherViewState when read', () {
+    // Act
+    final state = container.read(weatherViewModelProvider);
+
+    // Assert
+    expect(state, isA<WeatherViewState>());
+  });
+
+  test('reloadWeather updates state successfully', () {
+    // Arrange
+    final viewModel = container.read(weatherViewModelProvider.notifier);
+    provideDummy<Result<WeatherInfoEntity>>(expectedSuccessResult);
+    when(
+      mockWeatherRepository.getWeather(expectedWeatherTarget),
+    ).thenReturn(expectedSuccessResult);
+
+    // Act
+    viewModel.reloadWeather();
+
+    // Assert
+    expect(viewModel.state.errorMessage, null);
+    expect(
+      viewModel.state.weatherCondition,
+      expectedWeatherInfo.weatherCondition,
+    );
+    expect(
+      viewModel.state.minTemperature,
+      expectedWeatherInfo.minTemperature.toString(),
+    );
+    expect(
+      viewModel.state.maxTemperature,
+      expectedWeatherInfo.maxTemperature.toString(),
+    );
+    verify(mockWeatherRepository.getWeather(any)).called(1);
+  });
+
+  test('reloadWeather handles error successfully, '
+      'and the forecast remains a placeholder', () {
+    // Arrange
+    final viewModel = container.read(weatherViewModelProvider.notifier);
+    provideDummy<Result<WeatherInfoEntity>>(expectedFailureResult);
+    when(
+      mockWeatherRepository.getWeather(expectedWeatherTarget),
+    ).thenReturn(expectedFailureResult);
+
+    // Act
+    viewModel.reloadWeather();
+
+    // Assert
+    expect(viewModel.state.errorMessage, expectedErrorMessage);
+    expect(viewModel.state.weatherCondition, null);
+    expect(viewModel.state.minTemperature, placeholderTemperature);
+    expect(viewModel.state.maxTemperature, placeholderTemperature);
path: test/unit_tests/view_models/weather_view_model_test.dart
user: login: trm11tkr
body: ![imo-badge](https://img.shields.io/badge/review-imo-orange.svg) 
placeholderTemperature はこのテスト内でのみ使用されているので、テスト内で定義してあげると可読性が向上しそうに思いました。
created_at: 2025-04-22T08:59:57Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,158 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/application/weather/usecase_providers.dart';
+import 'package:flutter_training/application/weather/usecases/reload_weather_usecase.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/presentation/weather/view_models/weather_view_model.dart';
+import 'package:flutter_training/presentation/weather/view_states/weather_view_state.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_view_model_test.mocks.dart';
+
+/// TODO: verifyでgetWeather(expectedWeatherTarget)がエラーになるのを解決する
+/// WeatherViewModel内部でDateTime.now()が使われており、時間を変更するとテストが失敗する
+@GenerateNiceMocks([MockSpec<WeatherRepository>()])
+void main() {
+  // Arrange
+  late MockWeatherRepository mockWeatherRepository;
+  late ReloadWeatherUseCase reloadWeatherUseCase;
+  late ProviderContainer container;
+
+  final expectedWeatherTarget = createWeatherTarget();
+  final expectedWeatherInfo = createWeatherInfo(
+    date: expectedWeatherTarget.date, // 固定された日付を使用
+  );
+  final expectedErrorMessage = CommonErrorMessages.unknown.message;
+  final expectedSuccessResult = Success(expectedWeatherInfo);
+  final expectedFailureResult = Failure<WeatherInfoEntity>(
+    expectedErrorMessage,
+  );
+  const placeholderTemperature = '**';
+
+  setUp(() {
+    mockWeatherRepository = MockWeatherRepository();
+    reloadWeatherUseCase = ReloadWeatherUseCase(mockWeatherRepository);
+    container = ProviderContainer(
+      overrides: [
+        reloadWeatherUseCaseProvider.overrideWithValue(reloadWeatherUseCase),
+      ],
+    );
+  });
+
+  tearDown(() {
+    container.dispose();
+  });
+
+  test('weatherViewModelProvider returns WeatherViewState when read', () {
+    // Act
+    final state = container.read(weatherViewModelProvider);
+
+    // Assert
+    expect(state, isA<WeatherViewState>());
+  });
+
+  test('reloadWeather updates state successfully', () {
+    // Arrange
+    final viewModel = container.read(weatherViewModelProvider.notifier);
+    provideDummy<Result<WeatherInfoEntity>>(expectedSuccessResult);
+    when(
+      mockWeatherRepository.getWeather(expectedWeatherTarget),
+    ).thenReturn(expectedSuccessResult);
+
+    // Act
+    viewModel.reloadWeather();
+
+    // Assert
+    expect(viewModel.state.errorMessage, null);
+    expect(
+      viewModel.state.weatherCondition,
+      expectedWeatherInfo.weatherCondition,
+    );
+    expect(
+      viewModel.state.minTemperature,
+      expectedWeatherInfo.minTemperature.toString(),
+    );
+    expect(
+      viewModel.state.maxTemperature,
+      expectedWeatherInfo.maxTemperature.toString(),
+    );
+    verify(mockWeatherRepository.getWeather(any)).called(1);
+  });
+
+  test('reloadWeather handles error successfully, '
+      'and the forecast remains a placeholder', () {
+    // Arrange
+    final viewModel = container.read(weatherViewModelProvider.notifier);
+    provideDummy<Result<WeatherInfoEntity>>(expectedFailureResult);
+    when(
+      mockWeatherRepository.getWeather(expectedWeatherTarget),
+    ).thenReturn(expectedFailureResult);
+
+    // Act
+    viewModel.reloadWeather();
+
+    // Assert
+    expect(viewModel.state.errorMessage, expectedErrorMessage);
+    expect(viewModel.state.weatherCondition, null);
+    expect(viewModel.state.minTemperature, placeholderTemperature);
+    expect(viewModel.state.maxTemperature, placeholderTemperature);
+    verify(mockWeatherRepository.getWeather(any)).called(1);
+  });
+
+  test('reloadWeather handles error successfully, after state is updated', () {
path: test/unit_tests/view_models/weather_view_model_test.dart
user: login: trm11tkr
body: ![imo-badge](https://img.shields.io/badge/review-imo-orange.svg) 
今回のケースでは、テストを分ける方が良いかもしれないと思いました。

### テストを分けるメリット
- テスト失敗時の原因特定が明確になる
  - 「成功ケース」と「成功後のエラーケース」が別々になっていれば、エラーが起きた際に、どのシナリオでエラーが起きているのかすぐに特定できます
  - 一方、1つにまとめてしまうと、テスト失敗時に、最初の成功処理に問題があるのか、それとも2回目の失敗処理に問題があるのかが曖昧になりがちです
- テストがシンプルになり可読性が向上する
  - 1つのテストに1つの目的を持たせることで、後から見たときに「何を確認しているテストなのか」が分かりやすくなります

具体的には、「Arrange の段階で 成功状態にしておく」という流れですね。
created_at: 2025-04-23T00:50:31Z
author_association: COLLABORATOR
diff_hunk: @@ -71,7 +71,7 @@ jobs:
           fatal-infos: true
 
       - name: Run test
-        run: flutter test --no-pub --machine > test_report.log
+        run: flutter test --coverage --no-pub --machine > test_report.log
path: .github/workflows/check-pr.yaml
user: login: trm11tkr
body: ![good-badge](https://img.shields.io/badge/review-good-blightgreen.svg) 
🥰 ✨
created_at: 2025-04-23T00:59:07Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,31 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+
+void main() {
+  // Arrange
+  final expectedWeatherInfo = createWeatherInfo(date: null);
+  final expectedJson = {
+    'weather_condition': expectedWeatherInfo.weatherCondition.name,
+    'max_temperature': expectedWeatherInfo.maxTemperature,
+    'min_temperature': expectedWeatherInfo.minTemperature,
+    'date': expectedWeatherInfo.date.toIso8601String(),
+  };
+
+  test('toJson returns expected json', () {
+    // Act
+    final json = expectedWeatherInfo.toJson();
+
+    // Assert
+    expect(json, expectedJson);
+  });
+
+  test('fromJson returns expected weather info', () {
+    // Act
+    final weatherInfo = WeatherInfoEntity.fromJson(expectedJson);
+
+    // Assert
+    expect(weatherInfo, equals(expectedWeatherInfo));
path: test/unit_tests/entities/weather_info_entity_test.dart
user: login: hidekitux
body: 意図的に利用していますが、内部でラップされるなら不要そうなので他の箇所も見直そうかと思います👍
created_at: 2025-04-23T05:58:33Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,38 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/config/gen/assets.gen.dart';
+import 'package:flutter_training/domain/weather/constants/weather_condition.dart';
+
+void main() {
+  // Arrange
+  test('fromString returns expected WeatherCondition', () {
+    // Act
+    final sunny = WeatherCondition.fromString('sunny');
+    final cloudy = WeatherCondition.fromString('cloudy');
+    final rainy = WeatherCondition.fromString('rainy');
+
+    // Assert
+    expect(sunny, WeatherCondition.sunny);
+    expect(cloudy, WeatherCondition.cloudy);
+    expect(rainy, WeatherCondition.rainy);
+  });
+
+  test('fromString throws Exception, if value is invalid', () {
+    // Act and Assert
+    expect(
+      () => WeatherCondition.fromString('invalid'),
+      throwsA(isA<Exception>()),
+    );
+  });
path: test/unit_tests/constants/weather_condition_test.dart
user: login: hidekitux
body: 確かにExceptionの中身の確認をしたほうがよさそうなので、提案していただいたコードを利用しようかと思います👍
created_at: 2025-04-23T06:25:38Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,158 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/application/weather/usecase_providers.dart';
+import 'package:flutter_training/application/weather/usecases/reload_weather_usecase.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/presentation/weather/view_models/weather_view_model.dart';
+import 'package:flutter_training/presentation/weather/view_states/weather_view_state.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_view_model_test.mocks.dart';
+
+/// TODO: verifyでgetWeather(expectedWeatherTarget)がエラーになるのを解決する
+/// WeatherViewModel内部でDateTime.now()が使われており、時間を変更するとテストが失敗する
+@GenerateNiceMocks([MockSpec<WeatherRepository>()])
+void main() {
+  // Arrange
+  late MockWeatherRepository mockWeatherRepository;
+  late ReloadWeatherUseCase reloadWeatherUseCase;
+  late ProviderContainer container;
+
+  final expectedWeatherTarget = createWeatherTarget();
+  final expectedWeatherInfo = createWeatherInfo(
+    date: expectedWeatherTarget.date, // 固定された日付を使用
+  );
+  final expectedErrorMessage = CommonErrorMessages.unknown.message;
+  final expectedSuccessResult = Success(expectedWeatherInfo);
+  final expectedFailureResult = Failure<WeatherInfoEntity>(
+    expectedErrorMessage,
+  );
+  const placeholderTemperature = '**';
+
+  setUp(() {
+    mockWeatherRepository = MockWeatherRepository();
+    reloadWeatherUseCase = ReloadWeatherUseCase(mockWeatherRepository);
+    container = ProviderContainer(
+      overrides: [
+        reloadWeatherUseCaseProvider.overrideWithValue(reloadWeatherUseCase),
+      ],
+    );
+  });
+
+  tearDown(() {
+    container.dispose();
+  });
+
+  test('weatherViewModelProvider returns WeatherViewState when read', () {
+    // Act
+    final state = container.read(weatherViewModelProvider);
+
+    // Assert
+    expect(state, isA<WeatherViewState>());
path: test/unit_tests/view_models/weather_view_model_test.dart
user: login: hidekitux
body: ```suggestion
    expect(state, const WeatherViewState());
```

型だけじゃなくて中身の比較もしてあげるべきなのは確かにそうなのですが、havingではなくequalsを使うのはどうでしょうかね?
created_at: 2025-04-23T07:19:05Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,38 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/config/gen/assets.gen.dart';
+import 'package:flutter_training/domain/weather/constants/weather_condition.dart';
+
+void main() {
+  // Arrange
+  test('fromString returns expected WeatherCondition', () {
+    // Act
+    final sunny = WeatherCondition.fromString('sunny');
+    final cloudy = WeatherCondition.fromString('cloudy');
+    final rainy = WeatherCondition.fromString('rainy');
+
+    // Assert
+    expect(sunny, WeatherCondition.sunny);
+    expect(cloudy, WeatherCondition.cloudy);
+    expect(rainy, WeatherCondition.rainy);
+  });
+
+  test('fromString throws Exception, if value is invalid', () {
+    // Act and Assert
+    expect(
+      () => WeatherCondition.fromString('invalid'),
+      throwsA(isA<Exception>()),
+    );
+  });
path: test/unit_tests/constants/weather_condition_test.dart
user: login: hidekitux
body: https://github.com/hidekitux/yumemi-flutter-training/pull/23/commits/84e3144a2f1b912de9d4fc82813dc1f185637f5b にて対応しました!
created_at: 2025-04-23T07:38:11Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,158 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/application/weather/usecase_providers.dart';
+import 'package:flutter_training/application/weather/usecases/reload_weather_usecase.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/presentation/weather/view_models/weather_view_model.dart';
+import 'package:flutter_training/presentation/weather/view_states/weather_view_state.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_view_model_test.mocks.dart';
+
+/// TODO: verifyでgetWeather(expectedWeatherTarget)がエラーになるのを解決する
+/// WeatherViewModel内部でDateTime.now()が使われており、時間を変更するとテストが失敗する
+@GenerateNiceMocks([MockSpec<WeatherRepository>()])
+void main() {
+  // Arrange
+  late MockWeatherRepository mockWeatherRepository;
+  late ReloadWeatherUseCase reloadWeatherUseCase;
+  late ProviderContainer container;
+
+  final expectedWeatherTarget = createWeatherTarget();
+  final expectedWeatherInfo = createWeatherInfo(
+    date: expectedWeatherTarget.date, // 固定された日付を使用
+  );
+  final expectedErrorMessage = CommonErrorMessages.unknown.message;
+  final expectedSuccessResult = Success(expectedWeatherInfo);
+  final expectedFailureResult = Failure<WeatherInfoEntity>(
+    expectedErrorMessage,
+  );
+  const placeholderTemperature = '**';
+
+  setUp(() {
+    mockWeatherRepository = MockWeatherRepository();
+    reloadWeatherUseCase = ReloadWeatherUseCase(mockWeatherRepository);
+    container = ProviderContainer(
+      overrides: [
+        reloadWeatherUseCaseProvider.overrideWithValue(reloadWeatherUseCase),
+      ],
+    );
+  });
+
+  tearDown(() {
+    container.dispose();
+  });
+
+  test('weatherViewModelProvider returns WeatherViewState when read', () {
+    // Act
+    final state = container.read(weatherViewModelProvider);
+
+    // Assert
+    expect(state, isA<WeatherViewState>());
path: test/unit_tests/view_models/weather_view_model_test.dart
user: login: trm11tkr
body: おっしゃる通り 直接比較して良さそうに思いました！
created_at: 2025-04-23T07:40:57Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,31 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+
+void main() {
+  // Arrange
+  final expectedWeatherInfo = createWeatherInfo(date: null);
+  final expectedJson = {
+    'weather_condition': expectedWeatherInfo.weatherCondition.name,
+    'max_temperature': expectedWeatherInfo.maxTemperature,
+    'min_temperature': expectedWeatherInfo.minTemperature,
+    'date': expectedWeatherInfo.date.toIso8601String(),
+  };
+
+  test('toJson returns expected json', () {
+    // Act
+    final json = expectedWeatherInfo.toJson();
+
+    // Assert
+    expect(json, expectedJson);
+  });
+
+  test('fromJson returns expected weather info', () {
+    // Act
+    final weatherInfo = WeatherInfoEntity.fromJson(expectedJson);
+
+    // Assert
+    expect(weatherInfo, equals(expectedWeatherInfo));
path: test/unit_tests/entities/weather_info_entity_test.dart
user: login: hidekitux
body: [41f0b33](https://github.com/hidekitux/yumemi-flutter-training/pull/23/commits/41f0b3302ad5e7d4785e463e41ef6a3f3629e0c9) にて対応しました🙇
created_at: 2025-04-23T07:42:15Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,158 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/application/weather/usecase_providers.dart';
+import 'package:flutter_training/application/weather/usecases/reload_weather_usecase.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/presentation/weather/view_models/weather_view_model.dart';
+import 'package:flutter_training/presentation/weather/view_states/weather_view_state.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_view_model_test.mocks.dart';
+
+/// TODO: verifyでgetWeather(expectedWeatherTarget)がエラーになるのを解決する
+/// WeatherViewModel内部でDateTime.now()が使われており、時間を変更するとテストが失敗する
+@GenerateNiceMocks([MockSpec<WeatherRepository>()])
+void main() {
+  // Arrange
+  late MockWeatherRepository mockWeatherRepository;
+  late ReloadWeatherUseCase reloadWeatherUseCase;
+  late ProviderContainer container;
+
+  final expectedWeatherTarget = createWeatherTarget();
+  final expectedWeatherInfo = createWeatherInfo(
+    date: expectedWeatherTarget.date, // 固定された日付を使用
+  );
+  final expectedErrorMessage = CommonErrorMessages.unknown.message;
+  final expectedSuccessResult = Success(expectedWeatherInfo);
+  final expectedFailureResult = Failure<WeatherInfoEntity>(
+    expectedErrorMessage,
+  );
+  const placeholderTemperature = '**';
+
+  setUp(() {
+    mockWeatherRepository = MockWeatherRepository();
+    reloadWeatherUseCase = ReloadWeatherUseCase(mockWeatherRepository);
+    container = ProviderContainer(
+      overrides: [
+        reloadWeatherUseCaseProvider.overrideWithValue(reloadWeatherUseCase),
+      ],
+    );
+  });
+
+  tearDown(() {
+    container.dispose();
+  });
+
+  test('weatherViewModelProvider returns WeatherViewState when read', () {
+    // Act
+    final state = container.read(weatherViewModelProvider);
+
+    // Assert
+    expect(state, isA<WeatherViewState>());
+  });
+
+  test('reloadWeather updates state successfully', () {
+    // Arrange
+    final viewModel = container.read(weatherViewModelProvider.notifier);
+    provideDummy<Result<WeatherInfoEntity>>(expectedSuccessResult);
+    when(
+      mockWeatherRepository.getWeather(expectedWeatherTarget),
+    ).thenReturn(expectedSuccessResult);
+
+    // Act
+    viewModel.reloadWeather();
+
+    // Assert
+    expect(viewModel.state.errorMessage, null);
+    expect(
+      viewModel.state.weatherCondition,
+      expectedWeatherInfo.weatherCondition,
+    );
+    expect(
+      viewModel.state.minTemperature,
+      expectedWeatherInfo.minTemperature.toString(),
+    );
+    expect(
+      viewModel.state.maxTemperature,
+      expectedWeatherInfo.maxTemperature.toString(),
+    );
+    verify(mockWeatherRepository.getWeather(any)).called(1);
+  });
+
+  test('reloadWeather handles error successfully, '
+      'and the forecast remains a placeholder', () {
+    // Arrange
+    final viewModel = container.read(weatherViewModelProvider.notifier);
+    provideDummy<Result<WeatherInfoEntity>>(expectedFailureResult);
+    when(
+      mockWeatherRepository.getWeather(expectedWeatherTarget),
+    ).thenReturn(expectedFailureResult);
+
+    // Act
+    viewModel.reloadWeather();
+
+    // Assert
+    expect(viewModel.state.errorMessage, expectedErrorMessage);
+    expect(viewModel.state.weatherCondition, null);
+    expect(viewModel.state.minTemperature, placeholderTemperature);
+    expect(viewModel.state.maxTemperature, placeholderTemperature);
path: test/unit_tests/view_models/weather_view_model_test.dart
user: login: hidekitux
body: [315c97b](https://github.com/hidekitux/yumemi-flutter-training/pull/23/commits/315c97b241e434a2544a714a5e415eeb369af7fc) にて対応しました🙏
created_at: 2025-04-23T07:45:44Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,158 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/application/weather/usecase_providers.dart';
+import 'package:flutter_training/application/weather/usecases/reload_weather_usecase.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/presentation/weather/view_models/weather_view_model.dart';
+import 'package:flutter_training/presentation/weather/view_states/weather_view_state.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_view_model_test.mocks.dart';
+
+/// TODO: verifyでgetWeather(expectedWeatherTarget)がエラーになるのを解決する
+/// WeatherViewModel内部でDateTime.now()が使われており、時間を変更するとテストが失敗する
+@GenerateNiceMocks([MockSpec<WeatherRepository>()])
+void main() {
+  // Arrange
+  late MockWeatherRepository mockWeatherRepository;
+  late ReloadWeatherUseCase reloadWeatherUseCase;
+  late ProviderContainer container;
+
+  final expectedWeatherTarget = createWeatherTarget();
+  final expectedWeatherInfo = createWeatherInfo(
+    date: expectedWeatherTarget.date, // 固定された日付を使用
+  );
+  final expectedErrorMessage = CommonErrorMessages.unknown.message;
+  final expectedSuccessResult = Success(expectedWeatherInfo);
+  final expectedFailureResult = Failure<WeatherInfoEntity>(
+    expectedErrorMessage,
+  );
+  const placeholderTemperature = '**';
+
+  setUp(() {
+    mockWeatherRepository = MockWeatherRepository();
+    reloadWeatherUseCase = ReloadWeatherUseCase(mockWeatherRepository);
+    container = ProviderContainer(
+      overrides: [
+        reloadWeatherUseCaseProvider.overrideWithValue(reloadWeatherUseCase),
+      ],
+    );
+  });
+
+  tearDown(() {
+    container.dispose();
+  });
+
+  test('weatherViewModelProvider returns WeatherViewState when read', () {
+    // Act
+    final state = container.read(weatherViewModelProvider);
+
+    // Assert
+    expect(state, isA<WeatherViewState>());
path: test/unit_tests/view_models/weather_view_model_test.dart
user: login: hidekitux
body: [b48a5cd](https://github.com/hidekitux/yumemi-flutter-training/pull/23/commits/b48a5cde7669820d3d532f007669961817c8fdd1) にて変更しました👍
created_at: 2025-04-23T07:49:58Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,158 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/application/weather/usecase_providers.dart';
+import 'package:flutter_training/application/weather/usecases/reload_weather_usecase.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/presentation/weather/view_models/weather_view_model.dart';
+import 'package:flutter_training/presentation/weather/view_states/weather_view_state.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_view_model_test.mocks.dart';
+
+/// TODO: verifyでgetWeather(expectedWeatherTarget)がエラーになるのを解決する
+/// WeatherViewModel内部でDateTime.now()が使われており、時間を変更するとテストが失敗する
+@GenerateNiceMocks([MockSpec<WeatherRepository>()])
+void main() {
+  // Arrange
+  late MockWeatherRepository mockWeatherRepository;
+  late ReloadWeatherUseCase reloadWeatherUseCase;
+  late ProviderContainer container;
+
+  final expectedWeatherTarget = createWeatherTarget();
+  final expectedWeatherInfo = createWeatherInfo(
+    date: expectedWeatherTarget.date, // 固定された日付を使用
+  );
+  final expectedErrorMessage = CommonErrorMessages.unknown.message;
+  final expectedSuccessResult = Success(expectedWeatherInfo);
+  final expectedFailureResult = Failure<WeatherInfoEntity>(
+    expectedErrorMessage,
+  );
+  const placeholderTemperature = '**';
+
+  setUp(() {
+    mockWeatherRepository = MockWeatherRepository();
+    reloadWeatherUseCase = ReloadWeatherUseCase(mockWeatherRepository);
+    container = ProviderContainer(
+      overrides: [
+        reloadWeatherUseCaseProvider.overrideWithValue(reloadWeatherUseCase),
+      ],
+    );
+  });
+
+  tearDown(() {
+    container.dispose();
+  });
+
+  test('weatherViewModelProvider returns WeatherViewState when read', () {
+    // Act
+    final state = container.read(weatherViewModelProvider);
+
+    // Assert
+    expect(state, isA<WeatherViewState>());
+  });
+
+  test('reloadWeather updates state successfully', () {
+    // Arrange
+    final viewModel = container.read(weatherViewModelProvider.notifier);
+    provideDummy<Result<WeatherInfoEntity>>(expectedSuccessResult);
+    when(
+      mockWeatherRepository.getWeather(expectedWeatherTarget),
+    ).thenReturn(expectedSuccessResult);
+
+    // Act
+    viewModel.reloadWeather();
+
+    // Assert
+    expect(viewModel.state.errorMessage, null);
+    expect(
+      viewModel.state.weatherCondition,
+      expectedWeatherInfo.weatherCondition,
+    );
+    expect(
+      viewModel.state.minTemperature,
+      expectedWeatherInfo.minTemperature.toString(),
+    );
+    expect(
+      viewModel.state.maxTemperature,
+      expectedWeatherInfo.maxTemperature.toString(),
+    );
+    verify(mockWeatherRepository.getWeather(any)).called(1);
+  });
+
+  test('reloadWeather handles error successfully, '
+      'and the forecast remains a placeholder', () {
+    // Arrange
+    final viewModel = container.read(weatherViewModelProvider.notifier);
+    provideDummy<Result<WeatherInfoEntity>>(expectedFailureResult);
+    when(
+      mockWeatherRepository.getWeather(expectedWeatherTarget),
+    ).thenReturn(expectedFailureResult);
+
+    // Act
+    viewModel.reloadWeather();
+
+    // Assert
+    expect(viewModel.state.errorMessage, expectedErrorMessage);
+    expect(viewModel.state.weatherCondition, null);
+    expect(viewModel.state.minTemperature, placeholderTemperature);
+    expect(viewModel.state.maxTemperature, placeholderTemperature);
+    verify(mockWeatherRepository.getWeather(any)).called(1);
+  });
+
+  test('reloadWeather handles error successfully, after state is updated', () {
path: test/unit_tests/view_models/weather_view_model_test.dart
user: login: hidekitux
body: https://github.com/hidekitux/yumemi-flutter-training/pull/23/commits/e2ddd99da41dbc494d84bd32b13cdcd2e8f39d08 にて「Arrangeの段階で成功状態にしておく」ように実装しました
created_at: 2025-04-23T07:53:09Z
author_association: OWNER
path: test/unit_tests/repositories/weather_repository_test.dart
user: login: hidekitux
body: [3b3fb52](https://github.com/hidekitux/yumemi-flutter-training/pull/23/commits/3b3fb5239d436f6bee631bd289e388fc97637ff5) にて対応しました👍
created_at: 2025-04-23T07:56:38Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,101 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/constants/weather_error_messages.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/api/weather_service.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/infrastructure/weather/repository_providers.dart';
+import 'package:flutter_training/infrastructure/weather/service_providers.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:yumemi_weather/yumemi_weather.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_repository_test.mocks.dart';
+
+@GenerateNiceMocks([MockSpec<WeatherService>()])
+void main() {
+  // Arrange
+  final mockWeatherService = MockWeatherService();
+  final weatherRepository = WeatherRepository(mockWeatherService);
+  final weatherTarget = createWeatherTarget();
+
+  test('weatherRepositoryProvider creates WeatherRepository', () {
+    // Arrange
+    final container = ProviderContainer(
+      overrides: [weatherServiceProvider.overrideWithValue(mockWeatherService)],
+    );
+
+    // Act
+    final repository = container.read(weatherRepositoryProvider);
+
+    // Assert
+    expect(repository, isA<WeatherRepository>());
+
+    // Clean up
+    container.dispose();
+  });
+
+  test('getWeather returns expected weather info, if API call succeeds', () {
+    // Arrange
+    final expectedWeatherInfo = createWeatherInfo(date: weatherTarget.date);
+    when(mockWeatherService.fetchWeather(any)).thenReturn(expectedWeatherInfo);
+
+    // Act
+    final result = weatherRepository.getWeather(weatherTarget);
+
+    // Assert
+    expect(result, isA<Success<WeatherInfoEntity>>());
+    final weatherInfo = (result as Success<WeatherInfoEntity>).value;
+    expect(weatherInfo, equals(expectedWeatherInfo));
+    verify(mockWeatherService.fetchWeather(weatherTarget)).called(1);
+  });
+
+  for (final error in YumemiWeatherError.values) {
+    test('getWeather returns Failure, if API throws $error', () {
+      // Arrange
+      late Result<WeatherInfoEntity> result;
+      when(mockWeatherService.fetchWeather(any)).thenThrow(error);
+
+      // Act and Assert
+      expect(
+        () => result = weatherRepository.getWeather(weatherTarget),
+        returnsNormally,
+      );
path: test/unit_tests/repositories/weather_repository_test.dart
user: login: hidekitux
body: 元々、エラーがテスト実行時のログに出てしまうのを防ぎたくて試行錯誤していました。（`throwsA`では出ないのと同じように）
ただ結局同じようなことは簡単にはできそうになかったので、その名残で`returnsNormally`があるといったところです。
[b4c16b1](https://github.com/hidekitux/yumemi-flutter-training/pull/23/commits/b4c16b176a8e34e1474c41c487f3548e55eb9ff7) にて修正しました🙇
created_at: 2025-04-23T08:14:22Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,101 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/constants/weather_error_messages.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/api/weather_service.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/infrastructure/weather/repository_providers.dart';
+import 'package:flutter_training/infrastructure/weather/service_providers.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:yumemi_weather/yumemi_weather.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_repository_test.mocks.dart';
+
+@GenerateNiceMocks([MockSpec<WeatherService>()])
+void main() {
+  // Arrange
+  final mockWeatherService = MockWeatherService();
+  final weatherRepository = WeatherRepository(mockWeatherService);
+  final weatherTarget = createWeatherTarget();
+
+  test('weatherRepositoryProvider creates WeatherRepository', () {
+    // Arrange
+    final container = ProviderContainer(
+      overrides: [weatherServiceProvider.overrideWithValue(mockWeatherService)],
+    );
+
+    // Act
+    final repository = container.read(weatherRepositoryProvider);
+
+    // Assert
+    expect(repository, isA<WeatherRepository>());
+
+    // Clean up
+    container.dispose();
+  });
+
+  test('getWeather returns expected weather info, if API call succeeds', () {
+    // Arrange
+    final expectedWeatherInfo = createWeatherInfo(date: weatherTarget.date);
+    when(mockWeatherService.fetchWeather(any)).thenReturn(expectedWeatherInfo);
+
+    // Act
+    final result = weatherRepository.getWeather(weatherTarget);
+
+    // Assert
+    expect(result, isA<Success<WeatherInfoEntity>>());
+    final weatherInfo = (result as Success<WeatherInfoEntity>).value;
+    expect(weatherInfo, equals(expectedWeatherInfo));
+    verify(mockWeatherService.fetchWeather(weatherTarget)).called(1);
+  });
+
+  for (final error in YumemiWeatherError.values) {
+    test('getWeather returns Failure, if API throws $error', () {
+      // Arrange
+      late Result<WeatherInfoEntity> result;
+      when(mockWeatherService.fetchWeather(any)).thenThrow(error);
+
+      // Act and Assert
+      expect(
+        () => result = weatherRepository.getWeather(weatherTarget),
+        returnsNormally,
+      );
+      expect(
+        result,
+        isA<Failure<WeatherInfoEntity>>().having(
+          (e) => (e as Failure).message,
path: test/unit_tests/repositories/weather_repository_test.dart
user: login: hidekitux
body: 確かにその通りです!
[b4c16b1](https://github.com/hidekitux/yumemi-flutter-training/pull/23/commits/b4c16b176a8e34e1474c41c487f3548e55eb9ff7) にて修正しました🙏
created_at: 2025-04-23T08:15:03Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,158 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/application/weather/usecase_providers.dart';
+import 'package:flutter_training/application/weather/usecases/reload_weather_usecase.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/presentation/weather/view_models/weather_view_model.dart';
+import 'package:flutter_training/presentation/weather/view_states/weather_view_state.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_view_model_test.mocks.dart';
+
+/// TODO: verifyでgetWeather(expectedWeatherTarget)がエラーになるのを解決する
path: test/unit_tests/view_models/weather_view_model_test.dart
user: login: hidekitux
body: fec0e0653bd12e1689f74590a3d24c2340799ec0 にて`WeatherTargetEntity`を受け取るように変更しました
created_at: 2025-04-23T08:17:45Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,101 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/constants/weather_error_messages.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/api/weather_service.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/infrastructure/weather/repository_providers.dart';
+import 'package:flutter_training/infrastructure/weather/service_providers.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:yumemi_weather/yumemi_weather.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_repository_test.mocks.dart';
+
+@GenerateNiceMocks([MockSpec<WeatherService>()])
+void main() {
+  // Arrange
+  final mockWeatherService = MockWeatherService();
+  final weatherRepository = WeatherRepository(mockWeatherService);
+  final weatherTarget = createWeatherTarget();
+
+  test('weatherRepositoryProvider creates WeatherRepository', () {
+    // Arrange
+    final container = ProviderContainer(
+      overrides: [weatherServiceProvider.overrideWithValue(mockWeatherService)],
+    );
+
+    // Act
+    final repository = container.read(weatherRepositoryProvider);
+
+    // Assert
+    expect(repository, isA<WeatherRepository>());
+
+    // Clean up
+    container.dispose();
+  });
+
+  test('getWeather returns expected weather info, if API call succeeds', () {
+    // Arrange
+    final expectedWeatherInfo = createWeatherInfo(date: weatherTarget.date);
+    when(mockWeatherService.fetchWeather(any)).thenReturn(expectedWeatherInfo);
+
+    // Act
+    final result = weatherRepository.getWeather(weatherTarget);
+
+    // Assert
+    expect(result, isA<Success<WeatherInfoEntity>>());
+    final weatherInfo = (result as Success<WeatherInfoEntity>).value;
+    expect(weatherInfo, equals(expectedWeatherInfo));
path: test/unit_tests/repositories/weather_repository_test.dart
user: login: hidekitux
body: 4bad9815932c005f30b2781e1df8cf193eba4689 にてまとめてチェックするように変更しました👍
created_at: 2025-04-23T08:18:55Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,22 @@
+import 'package:flutter_training/domain/weather/constants/weather_condition.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/domain/weather/entities/weather_target_entity.dart';
+
+WeatherTargetEntity createWeatherTarget({
+  String area = 'tokyo',
+  DateTime? date,
+}) => WeatherTargetEntity(area: area, date: date ?? DateTime(2025, 4));
+
+/// createWeatherTargetを利用するテストにおいて、そのdateや他の値をdateに明示的に指定する
+/// createWeatherTargetを利用しないテストにおいて、nullや他の値をdateに明示的に指定する
+WeatherInfoEntity createWeatherInfo({
+  required DateTime? date,
path: test/fixtures/weather_fixtures.dart
user: login: hidekitux
body: - ユニットテスト全体で同じ`WeatherTargetEntity`と`WeatherInfoEntity`を利用するために、それを生成する関数を作っておきたい
- 基本的には引数は不要（関数内の固定値を使う）だが、値を変更できるようにしたい
  - 特に`date`は`WeatherTargetEntity`と`WeatherInfoEntity`で同じものを使うため、明示的に指定するようにしたい

といったところですが、確かに違和感がありますね。
`date`から`required`を消すと違和感はなくなりますが、↑こういう意図もあったのでどうするとよさそうでしょうかね?🤔
created_at: 2025-04-23T08:29:07Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,158 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/application/weather/usecase_providers.dart';
+import 'package:flutter_training/application/weather/usecases/reload_weather_usecase.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/presentation/weather/view_models/weather_view_model.dart';
+import 'package:flutter_training/presentation/weather/view_states/weather_view_state.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_view_model_test.mocks.dart';
+
+/// TODO: verifyでgetWeather(expectedWeatherTarget)がエラーになるのを解決する
path: test/unit_tests/view_models/weather_view_model_test.dart
user: login: trm11tkr
body: ![nits-badge](https://img.shields.io/badge/review-nits-green.svg) 
TODO コメントの削除をお願いします！
created_at: 2025-04-24T02:26:43Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,22 @@
+import 'package:flutter_training/domain/weather/constants/weather_condition.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/domain/weather/entities/weather_target_entity.dart';
+
+WeatherTargetEntity createWeatherTarget({
+  String area = 'tokyo',
+  DateTime? date,
+}) => WeatherTargetEntity(area: area, date: date ?? DateTime(2025, 4));
+
+/// createWeatherTargetを利用するテストにおいて、そのdateや他の値をdateに明示的に指定する
+/// createWeatherTargetを利用しないテストにおいて、nullや他の値をdateに明示的に指定する
+WeatherInfoEntity createWeatherInfo({
+  required DateTime? date,
path: test/fixtures/weather_fixtures.dart
user: login: trm11tkr
body: 森田が意図を 100% 汲み取れていないと思うので、ヒデちゃんの考えを本日のペア作業時間でお聞かせください！🙏
created_at: 2025-04-24T02:26:49Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,22 @@
+import 'package:flutter_training/domain/weather/constants/weather_condition.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/domain/weather/entities/weather_target_entity.dart';
+
+WeatherTargetEntity createWeatherTarget({
+  String area = 'tokyo',
+  DateTime? date,
+}) => WeatherTargetEntity(area: area, date: date ?? DateTime(2025, 4));
+
+/// createWeatherTargetを利用するテストにおいて、そのdateや他の値をdateに明示的に指定する
+/// createWeatherTargetを利用しないテストにおいて、nullや他の値をdateに明示的に指定する
+WeatherInfoEntity createWeatherInfo({
+  required DateTime? date,
path: test/fixtures/weather_fixtures.dart
user: login: hidekitux
body: [21f2342](https://github.com/hidekitux/yumemi-flutter-training/pull/23/commits/21f234280db1b54f381924b8acfe152e72c7c4cd) にて、`date`の`required`を消して対応しました👍
created_at: 2025-04-24T05:58:48Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,158 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_training/application/weather/usecase_providers.dart';
+import 'package:flutter_training/application/weather/usecases/reload_weather_usecase.dart';
+import 'package:flutter_training/domain/common/constants/common_error_messages.dart';
+import 'package:flutter_training/domain/common/interfaces/result.dart';
+import 'package:flutter_training/domain/weather/entities/weather_info_entity.dart';
+import 'package:flutter_training/infrastructure/weather/repositories/weather_repository.dart';
+import 'package:flutter_training/presentation/weather/view_models/weather_view_model.dart';
+import 'package:flutter_training/presentation/weather/view_states/weather_view_state.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../fixtures/weather_fixtures.dart';
+import 'weather_view_model_test.mocks.dart';
+
+/// TODO: verifyでgetWeather(expectedWeatherTarget)がエラーになるのを解決する
path: test/unit_tests/view_models/weather_view_model_test.dart
user: login: hidekitux
body: TODOコメント削除しました!🙏: [042fbef](https://github.com/hidekitux/yumemi-flutter-training/pull/23/commits/042fbef2e8a7d4342390e1a74d6de8bff94b5627) 
created_at: 2025-04-24T05:59:47Z
author_association: OWNER

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-22T05:16:54Z
body: Ready for review :rocket:
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-22T05:37:49Z
body: ## Code Metrics Report
| Coverage | Test Execution Time |
|---------:|--------------------:|
| 100.0%   | 5s                  |



---
Reported by [octocov](https://github.com/k1LoW/octocov)
<!-- octocov -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-23T07:50:51Z
body: ## Code Metrics Report
| Coverage | Test Execution Time |
|---------:|--------------------:|
| 100.0%   | 5s                  |


### Code coverage of files in pull request scope (100.0%)

|                                                                                                                  Files                                                                                                                  | Coverage |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------:|
| [lib/presentation/weather/view_models/weather_view_model.dart](https://github.com/hidekitux/yumemi-flutter-training/blob/b0eda939e18b6c3779c3fe2e69133283fb9228f6/lib%2Fpresentation%2Fweather%2Fview_models%2Fweather_view_model.dart) | 100.0%   |

---
Reported by [octocov](https://github.com/k1LoW/octocov)
<!-- octocov -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-23T07:58:34Z
body: ## Code Metrics Report
| Coverage | Test Execution Time |
|---------:|--------------------:|
| 100.0%   | 20s                 |


### Code coverage of files in pull request scope (100.0%)

|                                                                                                                  Files                                                                                                                  | Coverage |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------:|
| [lib/presentation/weather/view_models/weather_view_model.dart](https://github.com/hidekitux/yumemi-flutter-training/blob/3b3fb5239d436f6bee631bd289e388fc97637ff5/lib%2Fpresentation%2Fweather%2Fview_models%2Fweather_view_model.dart) | 100.0%   |

---
Reported by [octocov](https://github.com/k1LoW/octocov)
<!-- octocov -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-23T08:08:17Z
body: ## Code Metrics Report
| Coverage | Test Execution Time |
|---------:|--------------------:|
| 100.0%   | 5s                  |


### Code coverage of files in pull request scope (100.0%)

|                                                                                                                  Files                                                                                                                  | Coverage |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------:|
| [lib/presentation/weather/view_models/weather_view_model.dart](https://github.com/hidekitux/yumemi-flutter-training/blob/18023f0896d29a4b1cf7bde946976c359c00b64c/lib%2Fpresentation%2Fweather%2Fview_models%2Fweather_view_model.dart) | 100.0%   |

---
Reported by [octocov](https://github.com/k1LoW/octocov)
<!-- octocov -->
author_association: NONE
user: login: hidekitux
created_at: 2025-04-23T08:41:22Z
body: 一通り見直しましたので確認お願いいたします🙏
author_association: OWNER
user: login: github-actions[bot]
created_at: 2025-04-24T00:41:33Z
body: ## Code Metrics Report
| Coverage | Test Execution Time |
|---------:|--------------------:|
| 100.0%   | 21s                 |


### Code coverage of files in pull request scope (100.0%)

|                                                                                                                  Files                                                                                                                  | Coverage |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------:|
| [lib/presentation/weather/view_models/weather_view_model.dart](https://github.com/hidekitux/yumemi-flutter-training/blob/90f905c0c97bb4b44996672a75f3e3e2e5a1beef/lib%2Fpresentation%2Fweather%2Fview_models%2Fweather_view_model.dart) | 100.0%   |

---
Reported by [octocov](https://github.com/k1LoW/octocov)
<!-- octocov -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-24T05:55:26Z
body: ## Code Metrics Report
| Coverage | Test Execution Time |
|---------:|--------------------:|
| 100.0%   | 5s                  |


### Code coverage of files in pull request scope (100.0%)

|                                                                                                                  Files                                                                                                                  | Coverage |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------:|
| [lib/presentation/weather/view_models/weather_view_model.dart](https://github.com/hidekitux/yumemi-flutter-training/blob/a1b31881ca16639b9a314702f1804e9195203792/lib%2Fpresentation%2Fweather%2Fview_models%2Fweather_view_model.dart) | 100.0%   |

---
Reported by [octocov](https://github.com/k1LoW/octocov)
<!-- octocov -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-24T05:56:48Z
body: ## Code Metrics Report
| Coverage | Test Execution Time |
|---------:|--------------------:|
| 100.0%   | 6s                  |


### Code coverage of files in pull request scope (100.0%)

|                                                                                                                  Files                                                                                                                  | Coverage |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------:|
| [lib/presentation/weather/view_models/weather_view_model.dart](https://github.com/hidekitux/yumemi-flutter-training/blob/a1b31881ca16639b9a314702f1804e9195203792/lib%2Fpresentation%2Fweather%2Fview_models%2Fweather_view_model.dart) | 100.0%   |

---
Reported by [octocov](https://github.com/k1LoW/octocov)
<!-- octocov -->
author_association: NONE
user: login: hidekitux
created_at: 2025-04-24T06:27:58Z
body: レビューしていただきありがとうございます☺️
author_association: OWNER