散装、无酒精饮料所需的模拟杯口

  • 本文关键字:模拟 散装 flutter mocking
  • 更新时间 :
  • 英文 :


在我的项目中,我有一个需要bit的Bloc。我试着用testWidget来测试这个案例。这里有完整的文件可以复制我的资料,我希望你能轻松地运行我的案例。

lib/add_item/集团/add_item_bloc.dart

import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:poc_test_bloc/view_item/cubit/view_item_cubit.dart';
import 'package:poc_test_bloc/view_item/view_item.dart';
part 'add_item_event.dart';
part 'add_item_state.dart';
class AddItemBloc extends Bloc<AddItemEvent, AddItemState> {
AddItemBloc({
required ViewItemCubit viewItemCubit,
})  : _viewItemCubit = viewItemCubit,
super(const AddItemState()) {
on<TestEvent>(_onTestEvent);
}
final ViewItemCubit _viewItemCubit;
Future<void> _onTestEvent(
TestEvent event,
Emitter<AddItemState> emit,
) async {
await _viewItemCubit.fetchViewItems(event.value);
final viewItem = _viewItemCubit.state.viewItems;
emit(
state.copyWith(
filteredItems: viewItem,
),
);
}
}

lib/add_item/集团/add_item_event.dart

part of 'add_item_bloc.dart';
abstract class AddItemEvent extends Equatable {
const AddItemEvent();
}
class TestEvent extends AddItemEvent {
const TestEvent({required this.value});
final String value;
@override
List<Object?> get props => [value];
}

lib/add_item/集团/add_item_state.dart

part of 'add_item_bloc.dart';
class AddItemState extends Equatable {
const AddItemState({
this.filteredItems = const [],
this.message = '',
});
final List<ViewItem> filteredItems;
final String message;
AddItemState copyWith({
List<ViewItem>? filteredItems,
String? message,
}) {
return AddItemState(
filteredItems: filteredItems ?? this.filteredItems,
message: message ?? this.message,
);
}
@override
List<Object?> get props => [
filteredItems,
message,
];
}

/lib/view_item view_item.dart

import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part './view_item.g.dart';
@JsonSerializable()
class ViewItem extends Equatable {
const ViewItem({
required this.itemId,
required this.documentId,
required this.materialId,
required this.totalQuantity,
required this.materialDescription,
required this.materialNumber,
required this.documentNumber,
required this.assignedQuantity,
required this.wbsId,
required this.label,
});
factory ViewItem.fromJson(Map<String, dynamic> json) =>
_$ViewItemFromJson(json);
Map<String, dynamic> toJson() => _$ViewItemToJson(this);
static const ViewItem empty = ViewItem(
itemId: 0,
documentId: 0,
materialId: 0,
totalQuantity: 0,
materialDescription: '',
materialNumber: '',
documentNumber: 0,
assignedQuantity: 0,
wbsId: 0,
label: '',
);
final int itemId;
final int documentId;
final int materialId;
final double totalQuantity;
final String materialDescription;
final String materialNumber;
final int documentNumber;
final double assignedQuantity;
final int? wbsId;
final String label;
ViewItem copyWith({
int? item2locationId,
int? itemId,
int? documentId,
int? materialId,
double? totalQuantity,
String? materialDescription,
String? materialNumber,
int? documentNumber,
double? assignedQuantity,
int? wbsId,
String? label,
}) {
return ViewItem(
itemId: itemId ?? this.itemId,
documentId: documentId ?? this.documentId,
materialId: materialId ?? this.materialId,
totalQuantity: totalQuantity ?? this.totalQuantity,
materialNumber: materialNumber ?? this.materialNumber,
documentNumber: documentNumber ?? this.documentNumber,
assignedQuantity: assignedQuantity ?? this.assignedQuantity,
materialDescription: materialDescription ?? this.materialDescription,
wbsId: wbsId ?? this.wbsId,
label: label ?? this.label,
);
}
@override
List<Object?> get props => [
itemId,
documentId,
materialId,
totalQuantity,
materialDescription,
materialNumber,
documentNumber,
assignedQuantity,
wbsId,
label,
];
}

/lib/view_item view_item.g.dart

// GENERATED CODE - DO NOT MODIFY BY HAND
part of './view_item.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ViewItem _$ViewItemFromJson(Map<String, dynamic> json) => $checkedCreate(
'ViewItem',
json,
($checkedConvert) {
final val = ViewItem(
itemId: $checkedConvert('item_id', (v) => v as int),
documentId: $checkedConvert('document_id', (v) => v as int),
materialId: $checkedConvert('material_id', (v) => v as int),
totalQuantity:
$checkedConvert('total_quantity', (v) => (v as num).toDouble()),
materialDescription:
$checkedConvert('material_description', (v) => v as String),
materialNumber:
$checkedConvert('material_number', (v) => v as String),
documentNumber: $checkedConvert('document_number', (v) => v as int),
assignedQuantity: $checkedConvert(
'assigned_quantity', (v) => (v as num).toDouble()),
wbsId: $checkedConvert('wbs_id', (v) => v as int?),
label: $checkedConvert('label', (v) => v as String),
);
return val;
},
fieldKeyMap: const {
'itemId': 'item_id',
'documentId': 'document_id',
'materialId': 'material_id',
'totalQuantity': 'total_quantity',
'materialDescription': 'material_description',
'materialNumber': 'material_number',
'documentNumber': 'document_number',
'assignedQuantity': 'assigned_quantity',
'wbsId': 'wbs_id'
},
);
Map<String, dynamic> _$ViewItemToJson(ViewItem instance) => <String, dynamic>{
'item_id': instance.itemId,
'document_id': instance.documentId,
'material_id': instance.materialId,
'total_quantity': instance.totalQuantity,
'material_description': instance.materialDescription,
'material_number': instance.materialNumber,
'document_number': instance.documentNumber,
'assigned_quantity': instance.assignedQuantity,
'wbs_id': instance.wbsId,
'label': instance.label,
};

/lib/view_item/肘view_item_cubit.dart

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:poc_test_bloc/view_item/view_item.dart';
part 'view_item_state.dart';
class ViewItemCubit extends Cubit<ViewItemState> {
ViewItemCubit() : super(const ViewItemState());
Future<void> fetchViewItems(String documentNumber) async {
emit(state.copyWith(status: ViewItemStatus.loading));
try {
final viewItem1 = ViewItem.empty.copyWith(label: 'test_1');
final viewItem2 = ViewItem.empty.copyWith(label: 'test_2');
final viewItem3 = ViewItem.empty.copyWith(label: 'test_3');
emit(state.copyWith(
status: ViewItemStatus.success,
viewItems: [viewItem1, viewItem2, viewItem3],
));
} catch (_) {
emit(state.copyWith(status: ViewItemStatus.timeout, message: 'error'));
}
}
}

/lib/view_item/肘view_item_state.dart

part of 'view_item_cubit.dart';
enum ViewItemStatus { initial, loading, success, failure, timeout }
extension ViewItemStatusX on ViewItemStatus {
bool get isInitial => this == ViewItemStatus.initial;
bool get isLoading => this == ViewItemStatus.loading;
bool get isSuccess => this == ViewItemStatus.success;
bool get isFailure => this == ViewItemStatus.failure;
bool get isTimeout => this == ViewItemStatus.timeout;
}
@JsonSerializable()
class ViewItemState extends Equatable {
final ViewItemStatus status;
final List<ViewItem> viewItems;
final String? message;
const ViewItemState({
this.status = ViewItemStatus.initial,
this.viewItems = const [],
this.message,
});
ViewItemState copyWith({
ViewItemStatus? status,
List<ViewItem>? viewItems,
String? message,
}) {
return ViewItemState(
message: message ?? this.message,
status: status ?? this.status,
viewItems: viewItems ?? this.viewItems,
);
}
@override
List<Object?> get props => [status, viewItems, message];
}

lib/app.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:poc_test_bloc/add_item/bloc/add_item_bloc.dart';
import 'package:poc_test_bloc/view_item/cubit/view_item_cubit.dart';
import 'assignment_item_page.dart';
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<ViewItemCubit>(
create: (context) => ViewItemCubit(),
),
BlocProvider<AddItemBloc>(
create: (context) => AddItemBloc(
viewItemCubit: context.read<ViewItemCubit>(),
),
),
],
child: const AssignmentItemPage(),
);
}
}

lib/assignment_item_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:poc_test_bloc/add_item/bloc/add_item_bloc.dart';
import 'package:poc_test_bloc/view_item/cubit/view_item_cubit.dart';
class AssignmentItemPage extends StatefulWidget {
const AssignmentItemPage({Key? key}) : super(key: key);
@override
State<AssignmentItemPage> createState() => _AssignmentItemPageState();
}
class _AssignmentItemPageState extends State<AssignmentItemPage> {
@override
Widget build(BuildContext context) {
return BlocBuilder<ViewItemCubit, ViewItemState>(
builder: (context, state) {
return BlocBuilder<AddItemBloc, AddItemState>(
builder: (context, state) {
context.read<AddItemBloc>().add(const TestEvent(value: '1234'));
return const MaterialApp(
home: Scaffold(
body: Text('test app'),
),
);
},
);
},
);
}
}

lib/main.dart

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart';
import 'package:poc_test_bloc/app.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
HydratedBloc.storage = await HydratedStorage.build(
storageDirectory: kIsWeb
? HydratedStorage.webStorageDirectory
: await getTemporaryDirectory(),
);
runApp(const App());
}

测试/widget_test.dart

import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:poc_test_bloc/add_item/bloc/add_item_bloc.dart';
import 'package:poc_test_bloc/assignment_item_page.dart';
import 'package:poc_test_bloc/view_item/cubit/view_item_cubit.dart';
import 'package:poc_test_bloc/view_item/view_item.dart';
class MockViewItemCubit extends MockCubit<ViewItemState>
implements ViewItemCubit {}
void main() {
group('test test', () {
late MockViewItemCubit mockViewItemCubit;
setUp(() {
mockViewItemCubit = MockViewItemCubit();
});
testWidgets('test', (WidgetTester tester) async {
const viewItem1 = ViewItem(
itemId: 1,
documentId: 1,
materialId: 1,
totalQuantity: 1,
materialDescription: 'test',
materialNumber: '1234567890',
documentNumber: 1234567890,
assignedQuantity: 2,
wbsId: 1,
label: '1',
);
const viewItem2 = ViewItem(
itemId: 2,
documentId: 2,
materialId: 2,
totalQuantity: 2,
materialDescription: 'test',
materialNumber: '1234567891',
documentNumber: 1234567892,
assignedQuantity: 3,
wbsId: 2,
label: '2',
);
when(() => mockViewItemCubit.state).thenReturn(const ViewItemState(
status: ViewItemStatus.initial,
));
when(() => mockViewItemCubit.fetchViewItems('1234567890'))
.thenAnswer((_) async {
const ViewItemState(
status: ViewItemStatus.success,
viewItems: [viewItem1, viewItem2],
);
});
await tester.pumpWidget(
BlocProvider<ViewItemCubit>.value(
value: mockViewItemCubit,
child: BlocProvider<AddItemBloc>.value(
value: AddItemBloc(
viewItemCubit: mockViewItemCubit,
),
child: const MaterialApp(
home: AssignmentItemPage(),
),
),
),
);
});
});
}

pubspec.yaml

name: poc_test_bloc
description: A new Flutter project.
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
sdk: '>=2.19.2 <3.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
equatable: ^2.0.3
flutter_bloc: ^8.0.1
json_annotation: ^4.6.0
hydrated_bloc: ^9.1.0
path_provider: ^2.0.8
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
bloc_test: ^9.1.0
json_serializable: ^6.0.0
mocktail: ^0.3.0
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^2.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
#   - images/a_dot_burr.jpeg
#   - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
#   - family: Schyler
#     fonts:
#       - asset: fonts/Schyler-Regular.ttf
#       - asset: fonts/Schyler-Italic.ttf
#         style: italic
#   - family: Trajan Pro
#     fonts:
#       - asset: fonts/TrajanPro.ttf
#       - asset: fonts/TrajanPro_Bold.ttf
#         weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages

将运行flutter doctor -v的输出粘贴到这里。

[√] Flutter (Channel stable, 3.7.3, on Microsoft Windows [Version 10.0.19045.2728], locale pl-PL)
• Flutter version 3.7.3 on channel stable at C:Program Filesflutter
• Upstream repository https://github.com/flutter/flutter.git                                 
• Framework revision 9944297138 (8 weeks ago), 2023-02-08 15:46:04 -0800                     
• Engine revision 248290d6d5
• Dart version 2.19.2
• DevTools version 2.20.1
                           
[√] Windows Version (Installed version of Windows is version 10 or higher)                       
[!] Android toolchain - develop for Android devices (Android SDK version 33.0.0-rc4)
• Android SDK at C:UsersplradmkwAppDataLocalAndroidsdk
• Platform android-33, build-tools 33.0.0-rc4
• Java binary at: C:Program FilesJavajdk-11.0.11binjava
• Java version OpenJDK Runtime Environment Microsoft-22268 (build 11.0.11+9)
! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses
[√] Chrome - develop for the web
• Chrome at C:Program FilesGoogleChromeApplicationchrome.exe
[!] Visual Studio - develop for Windows (Visual Studio Community 2022 17.5.3)
• Visual Studio at C:Program FilesMicrosoft Visual Studio2022Community
• Visual Studio Community 2022 version 17.5.33516.290
• Windows 10 SDK version 10.0.22000.0
X Visual Studio is missing necessary components. Please re-run the Visual Studio installer for the "Desktop development with C++" workload, and include these components:
MSVC v142 - VS 2019 C++ x64/x86 build tools
- If there are multiple build tool versions available, install the latest
C++ CMake tools for Windows
Windows 10 SDK
[!] Android Studio (version 2022.1)
• Android Studio at C:Program FilesAndroidAndroid Studio
• Flutter plugin can be installed from:
https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
https://plugins.jetbrains.com/plugin/6351-dart
X Unable to find bundled Java version.
• Try updating or re-installing Android Studio.
[√] IntelliJ IDEA Community Edition (version 2022.1)
• IntelliJ at C:Program FilesJetBrainsIntelliJ IDEA Community Edition 2022.1
• Flutter plugin can be installed from:
https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
https://plugins.jetbrains.com/plugin/6351-dart
[√] VS Code (version 1.77.0)
• VS Code at C:UsersplradmkwAppDataLocalProgramsMicrosoft VS Code
• Flutter extension version 3.60.0
[√] Connected device (4 available)
• sdk gphone64 x86 64 (mobile) • emulator-5554 • android-x64    • Android 13 (API 33) (emulator)
• Windows (desktop)            • windows       • windows-x64    • Microsoft Windows [Version 10.0.19045.2728]
• Chrome (web)                 • chrome        • web-javascript • Google Chrome 111.0.5563.65
• Edge (web)                   • edge          • web-javascript • Microsoft Edge 111.0.1661.54
[√] HTTP Host Availability
• All required HTTP hosts are available
! Doctor found issues in 3 categories.

我期待什么?我想通过final viewItem = _viewItemCubit.state.viewItems;AddItemBloc中接收viewItems = [viewItem1, viewItem2],我在方法中stub它(viewItems):

when(() => mockViewItemCubit.fetchViewItems(any())).thenAnswer((_) async {
const ViewItemState(
status: ViewItemStatus.success,
viewItems: [viewItem1, viewItem2],
);
});

但是_viewItemCubit.state.viewItems;总是空的。我想用"real"用模拟ViewItemCubit实现AddItemBloc。我怎样才能正确地嘲笑它呢?

fetchViewItems实际上不返回任何东西,所以您不能通过返回值来存根它,而是在调用fetchViewItems时尝试存根Cubit的state

应该看起来像这样:

when(() => mockViewItemCubit.fetchViewItems('1234567890'))
.thenAnswer((_) async {
when(() => mockViewItemCubit.state).thenReturn(const ViewItemState(
status: ViewItemStatus.success,
viewItems: [viewItem1, viewItem2],
));
return;
});

其他选项包括制作一个假的Cubit实现。