feat(test): update oauth handling and improve preferences management

This commit is contained in:
mso7nueh
2025-12-01 23:51:40 +03:00
parent 9fcfac87d4
commit 965ae2f02b
11 changed files with 902 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('AppConfig', () {
setUp(() async {
// Инициализируем dotenv для тестов
dotenv.loadFromString(envString: '''
BASE_URL=https://api.example.com
DEV_BASE_URL=https://dev-api.example.com
''');
});
test('создание с buildType.prod - использует baseUrl', () {
final config = AppConfig(buildType: AppBuildType.prod);
expect(config.buildType, AppBuildType.prod);
expect(config.baseUrl, isNotEmpty);
});
test('создание с buildType.dev - использует devBaseUrl', () {
final config = AppConfig(buildType: AppBuildType.dev);
expect(config.buildType, AppBuildType.dev);
expect(config.baseUrl, isNotEmpty);
});
test('создание с buildType.tests - использует devBaseUrl', () {
final config = AppConfig(buildType: AppBuildType.tests);
expect(config.buildType, AppBuildType.tests);
expect(config.baseUrl, isNotEmpty);
});
test('setBaseUrl - изменяет baseUrl', () {
final config = AppConfig(buildType: AppBuildType.tests);
const newUrl = 'http://test.example.com';
config.setBaseUrl(newUrl);
expect(config.baseUrl, newUrl);
});
test('globalBlocProviders - возвращает список провайдеров', () {
final config = AppConfig(buildType: AppBuildType.tests);
final providers = config.globalBlocProviders;
expect(providers, isNotEmpty);
expect(providers.length, greaterThan(0));
});
test('localizationDelegates - возвращает список делегатов', () {
final config = AppConfig(buildType: AppBuildType.tests);
final delegates = config.localizationDelegates;
expect(delegates, isNotEmpty);
expect(delegates.length, greaterThan(0));
});
test('supportedLocales - возвращает список локалей', () {
final config = AppConfig(buildType: AppBuildType.tests);
final locales = config.supportedLocales;
expect(locales, isNotEmpty);
expect(locales.length, greaterThan(0));
});
});
}

View File

@@ -0,0 +1,36 @@
import 'package:eatvio_app/core/comments/models/comment_update_model.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('CommentUpdateModel', () {
test('создание модели через конструктор', () {
final model = CommentUpdateModel(body: 'Обновлённый текст комментария');
expect(model.body, 'Обновлённый текст комментария');
});
test('создание модели из JSON', () {
final json = {'body': 'Текст комментария'};
final model = CommentUpdateModel.fromJson(json);
expect(model.body, 'Текст комментария');
});
test('преобразование модели в JSON', () {
final model = CommentUpdateModel(body: 'Текст комментария');
final json = model.toJson();
expect(json['body'], 'Текст комментария');
});
test('равенство моделей', () {
final model1 = CommentUpdateModel(body: 'Текст комментария');
final model2 = CommentUpdateModel(body: 'Текст комментария');
final model3 = CommentUpdateModel(body: 'Другой текст');
expect(model1, equals(model2));
expect(model1, isNot(equals(model3)));
});
});
}

View File

@@ -0,0 +1,73 @@
import 'package:eatvio_app/core/cubit/base_cubit.dart';
import 'package:flutter_test/flutter_test.dart';
// Простой тестовый cubit для проверки BaseCubit
class TestState {
final String? value;
TestState({this.value});
}
class TestCubit extends BaseCubit<TestState> {
TestCubit() : super(TestState());
void testHandleError(Object error, [StackTrace? stackTrace]) {
handleError(error, stackTrace);
}
Future<String?> testSafeExecute() async {
return await safeExecute(() async => 'success');
}
Future<String?> testSafeExecuteWithError() async {
return await safeExecute(() async => throw Exception('Test error'));
}
Future<String?> testSafeExecuteWithState() async {
return await safeExecuteWithState<String>(
() async => 'result',
loadingState: TestState(value: 'loading'),
successState: (result) => TestState(value: result),
errorState: (error) => TestState(value: 'error'),
);
}
}
void main() {
group('BaseCubit', () {
late TestCubit testCubit;
setUp(() {
testCubit = TestCubit();
});
tearDown(() async {
if (!testCubit.isClosed) {
await testCubit.close();
}
});
test('handleError - обрабатывает ошибку без исключений', () {
expect(() => testCubit.testHandleError(Exception('Test error')), returnsNormally);
});
test('safeExecute - успешно выполняет операцию', () async {
final result = await testCubit.testSafeExecute();
expect(result, 'success');
});
test('safeExecute - возвращает null при ошибке', () async {
final result = await testCubit.testSafeExecuteWithError();
expect(result, isNull);
});
test('safeExecuteWithState - устанавливает состояния правильно', () async {
final result = await testCubit.testSafeExecuteWithState();
expect(result, 'result');
expect(testCubit.state.value, 'result');
});
});
}

View File

@@ -0,0 +1,98 @@
import 'package:eatvio_app/core/cubit/marketing/marketing_cubit.dart';
import 'package:eatvio_app/core/cubit/marketing/marketing_cubit_state.dart';
import 'package:eatvio_app/core/prefs.dart';
import 'package:eatvio_app/core/prefs_keys.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'marketing_cubit_test.mocks.dart';
@GenerateNiceMocks([MockSpec<Prefs>()])
void main() {
group('MarketingCubit', () {
late MarketingCubit marketingCubit;
late MockPrefs mockPrefs;
setUp(() async {
SharedPreferences.setMockInitialValues({});
mockPrefs = MockPrefs();
marketingCubit = MarketingCubit(mockPrefs);
});
tearDown(() async {
if (!marketingCubit.isClosed) {
await marketingCubit.close();
}
});
test('начальное состояние - MarketingCubitState', () {
expect(marketingCubit.state, const MarketingCubitState());
});
test('checkRegistrationDate - возвращает false если registrationDate отсутствует', () async {
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(null);
final result = await marketingCubit.checkRegistrationDate();
expect(result, isFalse);
verify(mockPrefs.getInt(PrefsKeys.registrationDate)).called(1);
});
test('checkRegistrationDate - возвращает false если маркетинговый bottom sheet уже был показан', () async {
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(DateTime.now().millisecondsSinceEpoch);
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).thenReturn(true);
final result = await marketingCubit.checkRegistrationDate();
expect(result, isFalse);
verify(mockPrefs.getInt(PrefsKeys.registrationDate)).called(1);
verify(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).called(1);
});
test('checkRegistrationDate - возвращает false если прошло менее 14 дней', () async {
final registrationDate = DateTime.now().subtract(const Duration(days: 10));
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(registrationDate.millisecondsSinceEpoch);
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).thenReturn(false);
final result = await marketingCubit.checkRegistrationDate();
expect(result, isFalse);
verify(mockPrefs.getInt(PrefsKeys.registrationDate)).called(1);
verify(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).called(1);
verifyNever(mockPrefs.setBool(any, any));
});
test('checkRegistrationDate - возвращает true если прошло 14 дней или больше', () async {
final registrationDate = DateTime.now().subtract(const Duration(days: 14));
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(registrationDate.millisecondsSinceEpoch);
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).thenReturn(false);
when(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true)).thenAnswer((_) async => Future.value());
final result = await marketingCubit.checkRegistrationDate();
expect(result, isTrue);
verify(mockPrefs.getInt(PrefsKeys.registrationDate)).called(1);
verify(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).called(1);
verify(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true)).called(1);
});
test('checkRegistrationDate - устанавливает флаг после показа bottom sheet', () async {
final registrationDate = DateTime.now().subtract(const Duration(days: 15));
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(registrationDate.millisecondsSinceEpoch);
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).thenReturn(false);
when(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true)).thenAnswer((_) async => Future.value());
await marketingCubit.checkRegistrationDate();
verify(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true)).called(1);
});
test('checkRegistrationDate - пробрасывает исключение', () async {
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenThrow(Exception('Ошибка чтения'));
expect(() => marketingCubit.checkRegistrationDate(), throwsException);
});
});
}

View File

@@ -0,0 +1,80 @@
import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_cubit.dart';
import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_state.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('InternetConnectionCubit', () {
late InternetConnectionCubit cubit;
const MethodChannel connectivityChannel = MethodChannel('dev.fluttercommunity.plus/connectivity');
setUp(() {
// Мокируем MethodChannel для connectivity
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
connectivityChannel,
(MethodCall methodCall) async {
if (methodCall.method == 'check') {
return ['wifi']; // ConnectivityResult.wifi как строка
}
if (methodCall.method == 'onConnectivityChanged') {
return null;
}
return null;
},
);
});
tearDown(() async {
// Очищаем моки
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
connectivityChannel,
null,
);
if (!cubit.isClosed) {
await cubit.close();
}
});
test('начальное состояние - isInternetAvailable = true', () {
cubit = InternetConnectionCubit();
expect(cubit.state.isInternetAvailable, isTrue);
});
test('checkConnection - успешная проверка с интернетом', () async {
cubit = InternetConnectionCubit();
// Ждем немного, чтобы cubit инициализировался
await Future.delayed(const Duration(milliseconds: 100));
// Проверяем, что метод checkConnection не выбрасывает исключений
await cubit.checkConnection();
// Состояние должно быть определено (true или false в зависимости от реального состояния)
expect(cubit.state, isA<InternetConnectionState>());
});
test('checkConnection - обработка PlatformException', () async {
// Этот тест сложно выполнить без реального мока Connectivity,
// так как Connectivity создается внутри cubit
// Оставим базовую проверку, что метод не падает
cubit = InternetConnectionCubit();
await Future.delayed(const Duration(milliseconds: 100));
// Просто проверяем, что метод выполняется без ошибок
await cubit.checkConnection();
expect(cubit.state, isA<InternetConnectionState>());
});
test('close - корректно закрывает cubit и освобождает ресурсы', () async {
cubit = InternetConnectionCubit();
await Future.delayed(const Duration(milliseconds: 100));
await cubit.close();
expect(cubit.isClosed, isTrue);
});
});
}

View File

@@ -0,0 +1,112 @@
import 'package:eatvio_app/core/token_storage.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
group('TokenStorage', () {
setUp(() async {
SharedPreferences.setMockInitialValues({});
});
test('haveTokenInStorage - возвращает false если токен отсутствует', () async {
final result = await TokenStorage.haveTokenInStorage();
expect(result, isFalse);
});
test('haveTokenInStorage - возвращает true если токен присутствует', () async {
SharedPreferences.setMockInitialValues({'token': 'test_token_123'});
final result = await TokenStorage.haveTokenInStorage();
expect(result, isTrue);
});
test('getTokenFromStorage - возвращает null если токен отсутствует', () async {
final result = await TokenStorage.getTokenFromStorage();
expect(result, isNull);
});
test('getTokenFromStorage - возвращает токен если он присутствует', () async {
const testToken = 'test_token_123';
SharedPreferences.setMockInitialValues({'token': testToken});
final result = await TokenStorage.getTokenFromStorage();
expect(result, testToken);
});
test('saveTokenInStorage - сохраняет токен', () async {
const testToken = 'new_token_456';
await TokenStorage.saveTokenInStorage(testToken);
final savedToken = await TokenStorage.getTokenFromStorage();
expect(savedToken, testToken);
});
test('removeTokenInStorage - удаляет токен', () async {
SharedPreferences.setMockInitialValues({'token': 'test_token_123'});
await TokenStorage.removeTokenInStorage();
final result = await TokenStorage.getTokenFromStorage();
expect(result, isNull);
});
});
group('SocketIdStorage', () {
setUp(() async {
SharedPreferences.setMockInitialValues({});
});
test('haveSocketIdInStorage - возвращает false если socketId отсутствует', () async {
final result = await SocketIdStorage.haveSocketIdInStorage();
expect(result, isFalse);
});
test('haveSocketIdInStorage - возвращает true если socketId присутствует', () async {
SharedPreferences.setMockInitialValues({'socketId': 'socket_id_123'});
final result = await SocketIdStorage.haveSocketIdInStorage();
expect(result, isTrue);
});
test('getSocketIdFromStorage - возвращает null если socketId отсутствует', () async {
final result = await SocketIdStorage.getSocketIdFromStorage();
expect(result, isNull);
});
test('getSocketIdFromStorage - возвращает socketId если он присутствует', () async {
const testSocketId = 'socket_id_123';
SharedPreferences.setMockInitialValues({'socketId': testSocketId});
final result = await SocketIdStorage.getSocketIdFromStorage();
expect(result, testSocketId);
});
test('saveSocketIdInStorage - сохраняет socketId', () async {
const testSocketId = 'new_socket_id_456';
await SocketIdStorage.saveSocketIdInStorage(testSocketId);
final savedSocketId = await SocketIdStorage.getSocketIdFromStorage();
expect(savedSocketId, testSocketId);
});
test('removeSocketIdInStorage - удаляет socketId', () async {
SharedPreferences.setMockInitialValues({'socketId': 'socket_id_123'});
await SocketIdStorage.removeSocketIdInStorage();
final result = await SocketIdStorage.getSocketIdFromStorage();
expect(result, isNull);
});
});
}

View File

@@ -0,0 +1,59 @@
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('Toasts', () {
setUp(() {
// Mock Fluttertoast plugin
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
const MethodChannel('PonnamKarthik/fluttertoast'),
(MethodCall methodCall) async {
return null;
},
);
});
tearDown(() {
// Clear mock handlers
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
const MethodChannel('PonnamKarthik/fluttertoast'),
null,
);
});
test('showInfoToast - вызывает Fluttertoast без ошибок', () {
expect(() => showInfoToast('Информационное сообщение'), returnsNormally);
});
test('showErrorToast - вызывает Fluttertoast без ошибок', () {
expect(() => showErrorToast('Сообщение об ошибке'), returnsNormally);
});
test('showSuccessToast - вызывает Fluttertoast без ошибок', () {
expect(() => showSuccessToast('Успешное сообщение'), returnsNormally);
});
test('showInternetConnectionToast - вызывает Fluttertoast без ошибок', () {
expect(() => showInternetConnectionToast(), returnsNormally);
});
test('showInfoToast - работает с пустой строкой', () {
expect(() => showInfoToast(''), returnsNormally);
});
test('showErrorToast - работает с длинным сообщением', () {
final longMessage = 'Очень длинное сообщение об ошибке ' * 10;
expect(() => showErrorToast(longMessage), returnsNormally);
});
test('showSuccessToast - работает с различными сообщениями', () {
expect(() => showSuccessToast('Операция выполнена успешно'), returnsNormally);
expect(() => showSuccessToast('Данные сохранены'), returnsNormally);
expect(() => showSuccessToast('Загрузка завершена'), returnsNormally);
});
});
}

View File

@@ -0,0 +1,154 @@
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
import 'package:eatvio_app/core/views/cubit/views_state.dart';
import 'package:eatvio_app/core/views/repository/views_repository.dart';
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'views_cubit_test.mocks.dart';
@GenerateNiceMocks([MockSpec<ViewsRepository>()])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('ViewsCubit', () {
late ViewsCubit cubit;
late MockViewsRepository mockViewsRepository;
setUp(() {
mockViewsRepository = MockViewsRepository();
cubit = ViewsCubit(viewsRepository: mockViewsRepository);
});
tearDown(() async {
if (!cubit.isClosed) {
await cubit.close();
}
});
test('начальное состояние - ViewsCubitState', () {
expect(cubit.state, isA<ViewsCubitState>());
});
test('handleVisibility - добавляет ID при visibleFraction >= 0.5', () async {
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
// Ждем выполнения таймера (1 секунда)
await Future.delayed(const Duration(milliseconds: 1100));
expect(cubit.postsIds, contains(1));
});
test('handleVisibility - не добавляет ID при visibleFraction < 0.5', () async {
cubit.handleVisibility(visibleFraction: 0.3, id: 1, type: ViewableContentType.posts);
await Future.delayed(const Duration(milliseconds: 100));
expect(cubit.postsIds, isEmpty);
});
test('handleVisibility - отменяет таймер при уменьшении visibleFraction', () async {
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
// Сначала видимость >= 0.5
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
await Future.delayed(const Duration(milliseconds: 500));
// Затем видимость < 0.5
cubit.handleVisibility(visibleFraction: 0.3, id: 1, type: ViewableContentType.posts);
await Future.delayed(const Duration(milliseconds: 700));
// ID не должен быть добавлен, так как таймер был отменен
expect(cubit.postsIds, isEmpty);
});
test('handleVisibility - не добавляет уже отправленный ID', () async {
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
// Добавляем и отправляем ID
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
await Future.delayed(const Duration(milliseconds: 1100));
await Future.delayed(const Duration(milliseconds: 4100)); // Ждем debounce и отправку
// Пытаемся добавить тот же ID снова
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
await Future.delayed(const Duration(milliseconds: 100));
// Таймер не должен быть создан, так как ID уже отправлен
expect(cubit.postsIds, isEmpty);
});
test('handleVisibility - работает для разных типов контента', () async {
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
cubit.handleVisibility(visibleFraction: 0.6, id: 2, type: ViewableContentType.plans);
cubit.handleVisibility(visibleFraction: 0.6, id: 3, type: ViewableContentType.diets);
cubit.handleVisibility(visibleFraction: 0.6, id: 4, type: ViewableContentType.recipes);
await Future.delayed(const Duration(milliseconds: 1100));
expect(cubit.postsIds, contains(1));
expect(cubit.plansIds, contains(2));
expect(cubit.dietsIds, contains(3));
expect(cubit.recipesIds, contains(4));
});
test('_sendViews - отправляет просмотры через repository', () async {
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
// Добавляем ID для разных типов
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
cubit.handleVisibility(visibleFraction: 0.6, id: 2, type: ViewableContentType.plans);
await Future.delayed(const Duration(milliseconds: 1100));
await Future.delayed(const Duration(milliseconds: 4100)); // Ждем debounce и отправку
// Проверяем, что repository был вызван минимум 2 раза (для posts и plans)
verify(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).called(greaterThanOrEqualTo(2));
});
test('close - отправляет оставшиеся просмотры и освобождает ресурсы', () async {
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
// Добавляем ID
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
await Future.delayed(const Duration(milliseconds: 1100));
// Очищаем предыдущие взаимодействия перед проверкой
clearInteractions(mockViewsRepository);
// Закрываем cubit
await cubit.close();
// Проверяем, что repository был вызван для отправки оставшихся просмотров
verify(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).called(greaterThanOrEqualTo(1));
expect(cubit.isClosed, isTrue);
});
test('_sendViews - не отправляет уже отправленные ID', () async {
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
// Добавляем и отправляем ID
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
await Future.delayed(const Duration(milliseconds: 1100));
await Future.delayed(const Duration(milliseconds: 4100));
// Очищаем моки
clearInteractions(mockViewsRepository);
// Закрываем cubit
await cubit.close();
// Repository не должен быть вызван, так как все ID уже отправлены
verifyNever(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel')));
});
});
}

View File

@@ -0,0 +1,71 @@
import 'package:eatvio_app/core/views/repository/views_repository.dart';
import 'package:eatvio_app/core/views/service/views_service.dart';
import 'package:eatvio_app/features/feed/models/view_model.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'views_repository_test.mocks.dart';
@GenerateNiceMocks([MockSpec<ViewsService>()])
void main() {
group('ViewsRepository', () {
late ViewsRepository viewsRepository;
late MockViewsService mockViewsService;
setUp(() {
mockViewsService = MockViewsService();
viewsRepository = ViewsRepository(viewsService: mockViewsService);
});
test('viewContent - успешная отправка просмотров', () async {
final viewModel = ViewModel(
type: 'blog_posts',
ids: [1, 2, 3],
);
when(mockViewsService.viewContent(viewModel: viewModel))
.thenAnswer((_) async => Future.value());
await viewsRepository.viewContent(viewModel: viewModel);
verify(mockViewsService.viewContent(viewModel: viewModel)).called(1);
});
test('viewContent - пробрасывает исключение', () async {
final viewModel = ViewModel(
type: 'blog_posts',
ids: [1, 2, 3],
);
when(mockViewsService.viewContent(viewModel: viewModel))
.thenThrow(Exception('Ошибка отправки'));
expect(
() => viewsRepository.viewContent(viewModel: viewModel),
throwsException,
);
});
test('viewContent - работает с разными типами контента', () async {
final postsModel = ViewModel(type: 'blog_posts', ids: [1]);
final plansModel = ViewModel(type: 'plans', ids: [2]);
final dietsModel = ViewModel(type: 'diets', ids: [3]);
final recipesModel = ViewModel(type: 'recipes', ids: [4]);
when(mockViewsService.viewContent(viewModel: anyNamed('viewModel')))
.thenAnswer((_) async => Future.value());
await viewsRepository.viewContent(viewModel: postsModel);
await viewsRepository.viewContent(viewModel: plansModel);
await viewsRepository.viewContent(viewModel: dietsModel);
await viewsRepository.viewContent(viewModel: recipesModel);
verify(mockViewsService.viewContent(viewModel: postsModel)).called(1);
verify(mockViewsService.viewContent(viewModel: plansModel)).called(1);
verify(mockViewsService.viewContent(viewModel: dietsModel)).called(1);
verify(mockViewsService.viewContent(viewModel: recipesModel)).called(1);
});
});
}

View File

@@ -0,0 +1,42 @@
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('NewMessagesCubit', () {
late NewMessagesCubit newMessagesCubit;
setUp(() {
newMessagesCubit = NewMessagesCubit();
});
tearDown(() async {
if (!newMessagesCubit.isClosed) {
await newMessagesCubit.close();
}
});
test('начальное состояние - NewMessagesState.empty', () {
expect(newMessagesCubit.state, const NewMessagesState.empty());
});
test('incrementCounter - увеличивает счётчик сообщений', () {
newMessagesCubit.incrementCounter();
expect(newMessagesCubit.state, isA<NewMessagesStateLoaded>());
final loadedState = newMessagesCubit.state as NewMessagesStateLoaded;
expect(loadedState.messagesCount, 1);
});
test('incrementCounter - увеличивает счётчик несколько раз', () {
newMessagesCubit.incrementCounter();
newMessagesCubit.incrementCounter();
newMessagesCubit.incrementCounter();
expect(newMessagesCubit.state, isA<NewMessagesStateLoaded>());
final loadedState = newMessagesCubit.state as NewMessagesStateLoaded;
expect(loadedState.messagesCount, 3);
});
});
}

View File

@@ -0,0 +1,103 @@
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_state.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'consultants_cubit_test.mocks.dart';
@GenerateNiceMocks([MockSpec<FeedRepository>()])
void main() {
group('ConsultantsCubit', () {
late ConsultantsCubit consultantsCubit;
late MockFeedRepository mockFeedRepository;
setUp(() {
mockFeedRepository = MockFeedRepository();
consultantsCubit = ConsultantsCubit(feedRepository: mockFeedRepository);
});
tearDown(() async {
if (!consultantsCubit.isClosed) {
await consultantsCubit.close();
}
});
test('начальное состояние - ConsultantsState', () {
expect(consultantsCubit.state, const ConsultantsState());
});
test('getConsultants - успешная загрузка консультантов', () async {
final testConsultants = [
FeedConsultantModel(
slug: 'consultant-1',
name: 'Консультант 1',
description: 'Описание консультанта 1',
totalScores: 100,
photo: 'https://example.com/icon1.jpg',
badges: [],
ratingPlace: 1,
),
FeedConsultantModel(
slug: 'consultant-2',
name: 'Консультант 2',
description: 'Описание консультанта 2',
totalScores: 200,
photo: 'https://example.com/icon2.jpg',
badges: [],
ratingPlace: 2,
),
];
when(mockFeedRepository.getConsultants()).thenAnswer((_) async => testConsultants);
await consultantsCubit.getConsultants();
await Future.delayed(const Duration(milliseconds: 100));
expect(consultantsCubit.state, isA<ConsultantsStateLoaded>());
final loadedState = consultantsCubit.state as ConsultantsStateLoaded;
expect(loadedState.consultantsList.length, 2);
expect(loadedState.isLoading, false);
verify(mockFeedRepository.getConsultants()).called(1);
});
test('getConsultants - переход в состояние loading', () async {
when(mockFeedRepository.getConsultants()).thenAnswer((_) async {
await Future.delayed(const Duration(milliseconds: 50));
return [];
});
final future = consultantsCubit.getConsultants();
await Future.delayed(const Duration(milliseconds: 10));
expect(consultantsCubit.state, const ConsultantsState.loading());
await future;
});
test('getConsultants - обработка ошибки', () async {
when(mockFeedRepository.getConsultants()).thenThrow(Exception('Ошибка загрузки'));
await consultantsCubit.getConsultants();
await Future.delayed(const Duration(milliseconds: 100));
expect(consultantsCubit.state, isA<ConsultantsStateError>());
final errorState = consultantsCubit.state as ConsultantsStateError;
expect(errorState.errorMessage, contains('Exception'));
});
test('getConsultants - загрузка с пустым списком', () async {
when(mockFeedRepository.getConsultants()).thenAnswer((_) async => []);
await consultantsCubit.getConsultants();
await Future.delayed(const Duration(milliseconds: 100));
expect(consultantsCubit.state, isA<ConsultantsStateLoaded>());
final loadedState = consultantsCubit.state as ConsultantsStateLoaded;
expect(loadedState.consultantsList, isEmpty);
expect(loadedState.isLoading, false);
});
});
}