import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:mini_tias/services/raw_capture_service.dart';
import 'package:mini_tias/services/sound_service.dart';
/// テスト用: RawCaptureService をスタブする.
///
/// プラットフォームチャネルを呼び出す代わりに呼び出し記録のみを行う.
class _FakeRawCaptureService extends RawCaptureService {
int shutterSoundCallCount = 0;
int saveCompleteSoundCallCount = 0;
bool shouldThrow = false;
@override
Future<void> playShutterSound() async {
if (shouldThrow) throw Exception('テスト用エラー');
shutterSoundCallCount++;
}
@override
Future<void> playSaveCompleteSound() async {
if (shouldThrow) throw Exception('テスト用エラー');
saveCompleteSoundCallCount++;
}
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() {
SharedPreferences.setMockInitialValues({});
});
group('SoundService', () {
group('初期状態', () {
test('デフォルトで isSoundEnabled が true である', () {
final service = SoundService(
rawCaptureService: _FakeRawCaptureService(),
);
expect(service.isSoundEnabled, isTrue);
});
});
group('toggleSound()', () {
test('1 回呼び出すと isSoundEnabled が false になる', () async {
final service = SoundService(
rawCaptureService: _FakeRawCaptureService(),
);
await service.toggleSound();
expect(service.isSoundEnabled, isFalse);
});
test('2 回呼び出すと isSoundEnabled が true に戻る', () async {
final service = SoundService(
rawCaptureService: _FakeRawCaptureService(),
);
await service.toggleSound();
await service.toggleSound();
expect(service.isSoundEnabled, isTrue);
});
test('切り替え後の値が SharedPreferences に永続化される', () async {
final service = SoundService(
rawCaptureService: _FakeRawCaptureService(),
);
await service.toggleSound(); // false に切り替え
final prefs = await SharedPreferences.getInstance();
expect(prefs.getBool('sound_enabled'), isFalse);
});
test('再度切り替えると SharedPreferences の値も true に更新される', () async {
final service = SoundService(
rawCaptureService: _FakeRawCaptureService(),
);
await service.toggleSound(); // false
await service.toggleSound(); // true
final prefs = await SharedPreferences.getInstance();
expect(prefs.getBool('sound_enabled'), isTrue);
});
});
group('init()', () {
test(
'SharedPreferences に sound_enabled=false が保存されていれば false を読み込む',
() async {
SharedPreferences.setMockInitialValues({'sound_enabled': false});
final service = SoundService(
rawCaptureService: _FakeRawCaptureService(),
);
await service.init();
expect(service.isSoundEnabled, isFalse);
},
);
test(
'SharedPreferences に sound_enabled=true が保存されていれば true を読み込む',
() async {
SharedPreferences.setMockInitialValues({'sound_enabled': true});
final service = SoundService(
rawCaptureService: _FakeRawCaptureService(),
);
await service.init();
expect(service.isSoundEnabled, isTrue);
},
);
test('SharedPreferences にキーが存在しない場合はデフォルト true を使う', () async {
SharedPreferences.setMockInitialValues({});
final service = SoundService(
rawCaptureService: _FakeRawCaptureService(),
);
await service.init();
expect(service.isSoundEnabled, isTrue);
});
});
group('playShutterSound()', () {
test(
'isSoundEnabled が true のとき RawCaptureService.playShutterSound() を呼ぶ',
() async {
final fake = _FakeRawCaptureService();
final service = SoundService(rawCaptureService: fake);
// デフォルトで isSoundEnabled == true
await service.playShutterSound();
expect(fake.shutterSoundCallCount, 1);
},
);
test(
'isSoundEnabled が false のとき RawCaptureService.playShutterSound() を呼ばない',
() async {
final fake = _FakeRawCaptureService();
final service = SoundService(rawCaptureService: fake);
await service.toggleSound(); // false に切り替え
await service.playShutterSound();
expect(fake.shutterSoundCallCount, 0);
},
);
test('RawCaptureService がエラーを投げても例外がリスロウされない', () async {
final fake = _FakeRawCaptureService()..shouldThrow = true;
final service = SoundService(rawCaptureService: fake);
// 例外なく完了することを確認
await expectLater(service.playShutterSound(), completes);
});
});
group('playSaveCompleteSound()', () {
test(
'isSoundEnabled が true のとき RawCaptureService.playSaveCompleteSound() を呼ぶ',
() async {
final fake = _FakeRawCaptureService();
final service = SoundService(rawCaptureService: fake);
await service.playSaveCompleteSound();
expect(fake.saveCompleteSoundCallCount, 1);
},
);
test(
'isSoundEnabled が false のとき RawCaptureService.playSaveCompleteSound() を呼ばない',
() async {
final fake = _FakeRawCaptureService();
final service = SoundService(rawCaptureService: fake);
await service.toggleSound(); // false に切り替え
await service.playSaveCompleteSound();
expect(fake.saveCompleteSoundCallCount, 0);
},
);
test('RawCaptureService がエラーを投げても例外がリスロウされない', () async {
final fake = _FakeRawCaptureService()..shouldThrow = true;
final service = SoundService(rawCaptureService: fake);
await expectLater(service.playSaveCompleteSound(), completes);
});
});
group('複合シナリオ', () {
test('init() 後に toggleSound() すると値が反転する', () async {
SharedPreferences.setMockInitialValues({'sound_enabled': false});
final service = SoundService(
rawCaptureService: _FakeRawCaptureService(),
);
await service.init();
expect(service.isSoundEnabled, isFalse);
await service.toggleSound();
expect(service.isSoundEnabled, isTrue);
});
test('サウンド ON の状態で複数回 playShutterSound() を呼ぶと呼び出し回数分だけ実行される', () async {
final fake = _FakeRawCaptureService();
final service = SoundService(rawCaptureService: fake);
await service.playShutterSound();
await service.playShutterSound();
await service.playShutterSound();
expect(fake.shutterSoundCallCount, 3);
});
});
});
}