@aicgen/aicgen 1.0.0-beta.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/rules/api-design.md +649 -0
- package/.agent/rules/architecture.md +2507 -0
- package/.agent/rules/best-practices.md +622 -0
- package/.agent/rules/code-style.md +308 -0
- package/.agent/rules/design-patterns.md +577 -0
- package/.agent/rules/devops.md +230 -0
- package/.agent/rules/error-handling.md +417 -0
- package/.agent/rules/instructions.md +28 -0
- package/.agent/rules/language.md +786 -0
- package/.agent/rules/performance.md +710 -0
- package/.agent/rules/security.md +587 -0
- package/.agent/rules/testing.md +572 -0
- package/.agent/workflows/add-documentation.md +10 -0
- package/.agent/workflows/generate-integration-tests.md +10 -0
- package/.agent/workflows/generate-unit-tests.md +11 -0
- package/.agent/workflows/performance-audit.md +11 -0
- package/.agent/workflows/refactor-extract-module.md +12 -0
- package/.agent/workflows/security-audit.md +12 -0
- package/.gemini/instructions.md +4843 -0
- package/AGENTS.md +9 -11
- package/bun.lock +755 -4
- package/claude.md +2 -2
- package/config.example.yml +129 -0
- package/config.yml +38 -0
- package/data/guideline-mappings.yml +128 -0
- package/data/language/dart/async.md +289 -0
- package/data/language/dart/basics.md +280 -0
- package/data/language/dart/error-handling.md +355 -0
- package/data/language/dart/index.md +10 -0
- package/data/language/dart/testing.md +352 -0
- package/data/language/swift/basics.md +477 -0
- package/data/language/swift/concurrency.md +654 -0
- package/data/language/swift/error-handling.md +679 -0
- package/data/language/swift/swiftui-mvvm.md +795 -0
- package/data/language/swift/testing.md +708 -0
- package/data/version.json +10 -8
- package/dist/index.js +50295 -29101
- package/jest.config.js +46 -0
- package/package.json +13 -2
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# Testing in Dart
|
|
2
|
+
|
|
3
|
+
## Unit Testing
|
|
4
|
+
|
|
5
|
+
```dart
|
|
6
|
+
import 'package:test/test.dart';
|
|
7
|
+
|
|
8
|
+
// ✅ Arrange-Act-Assert pattern
|
|
9
|
+
void main() {
|
|
10
|
+
group('UserService', () {
|
|
11
|
+
test('should create user with hashed password', () async {
|
|
12
|
+
// Arrange
|
|
13
|
+
final repository = MockUserRepository();
|
|
14
|
+
final service = UserService(repository);
|
|
15
|
+
final userData = UserData(email: 'test@example.com', password: 'secret');
|
|
16
|
+
|
|
17
|
+
// Act
|
|
18
|
+
final user = await service.createUser(userData);
|
|
19
|
+
|
|
20
|
+
// Assert
|
|
21
|
+
expect(user.email, equals('test@example.com'));
|
|
22
|
+
expect(user.passwordHash, isNot(equals('secret')));
|
|
23
|
+
verify(repository.save(any)).called(1);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('should throw ValidationException for invalid email', () {
|
|
27
|
+
final service = UserService(MockUserRepository());
|
|
28
|
+
|
|
29
|
+
expect(
|
|
30
|
+
() => service.createUser(UserData(email: 'invalid', password: 'pass')),
|
|
31
|
+
throwsA(isA<ValidationException>()),
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Test Organization
|
|
39
|
+
|
|
40
|
+
```dart
|
|
41
|
+
void main() {
|
|
42
|
+
// ✅ Use group for related tests
|
|
43
|
+
group('Calculator', () {
|
|
44
|
+
late Calculator calculator;
|
|
45
|
+
|
|
46
|
+
// setUp runs before each test
|
|
47
|
+
setUp(() {
|
|
48
|
+
calculator = Calculator();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// tearDown runs after each test
|
|
52
|
+
tearDown(() {
|
|
53
|
+
calculator.dispose();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('adds two numbers', () {
|
|
57
|
+
expect(calculator.add(2, 3), equals(5));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('subtracts two numbers', () {
|
|
61
|
+
expect(calculator.subtract(5, 3), equals(2));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
group('division', () {
|
|
65
|
+
test('divides two numbers', () {
|
|
66
|
+
expect(calculator.divide(10, 2), equals(5));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('throws on division by zero', () {
|
|
70
|
+
expect(
|
|
71
|
+
() => calculator.divide(10, 0),
|
|
72
|
+
throwsA(isA<DivisionByZeroException>()),
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Matchers
|
|
81
|
+
|
|
82
|
+
```dart
|
|
83
|
+
// Equality
|
|
84
|
+
expect(value, equals(expected));
|
|
85
|
+
expect(value, isNot(equals(unexpected)));
|
|
86
|
+
|
|
87
|
+
// Types
|
|
88
|
+
expect(value, isA<String>());
|
|
89
|
+
expect(value, isNotNull);
|
|
90
|
+
expect(value, isNull);
|
|
91
|
+
|
|
92
|
+
// Numbers
|
|
93
|
+
expect(value, greaterThan(5));
|
|
94
|
+
expect(value, lessThan(10));
|
|
95
|
+
expect(value, closeTo(3.14, 0.01));
|
|
96
|
+
|
|
97
|
+
// Strings
|
|
98
|
+
expect(text, contains('hello'));
|
|
99
|
+
expect(text, startsWith('Hello'));
|
|
100
|
+
expect(text, endsWith('world'));
|
|
101
|
+
expect(text, matches(r'^\d+$'));
|
|
102
|
+
|
|
103
|
+
// Collections
|
|
104
|
+
expect(list, isEmpty);
|
|
105
|
+
expect(list, isNotEmpty);
|
|
106
|
+
expect(list, hasLength(3));
|
|
107
|
+
expect(list, contains(item));
|
|
108
|
+
expect(map, containsValue(value));
|
|
109
|
+
|
|
110
|
+
// Futures
|
|
111
|
+
expect(future, completes);
|
|
112
|
+
expect(future, throwsException);
|
|
113
|
+
expect(future, completion(equals(value)));
|
|
114
|
+
|
|
115
|
+
// Custom matchers
|
|
116
|
+
expect(user, isA<User>().having((u) => u.email, 'email', 'test@example.com'));
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Mocking with Mockito
|
|
120
|
+
|
|
121
|
+
```dart
|
|
122
|
+
import 'package:mockito/mockito.dart';
|
|
123
|
+
import 'package:mockito/annotations.dart';
|
|
124
|
+
|
|
125
|
+
// Generate mocks
|
|
126
|
+
@GenerateMocks([UserRepository, ApiClient])
|
|
127
|
+
void main() {
|
|
128
|
+
group('UserService', () {
|
|
129
|
+
late MockUserRepository repository;
|
|
130
|
+
late UserService service;
|
|
131
|
+
|
|
132
|
+
setUp(() {
|
|
133
|
+
repository = MockUserRepository();
|
|
134
|
+
service = UserService(repository);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('should fetch user by id', () async {
|
|
138
|
+
// Arrange - stub method
|
|
139
|
+
final expectedUser = User(id: '1', name: 'Alice');
|
|
140
|
+
when(repository.findById('1')).thenAnswer((_) async => expectedUser);
|
|
141
|
+
|
|
142
|
+
// Act
|
|
143
|
+
final user = await service.getUser('1');
|
|
144
|
+
|
|
145
|
+
// Assert
|
|
146
|
+
expect(user, equals(expectedUser));
|
|
147
|
+
verify(repository.findById('1')).called(1);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('should handle not found', () async {
|
|
151
|
+
// Stub to return null
|
|
152
|
+
when(repository.findById(any)).thenAnswer((_) async => null);
|
|
153
|
+
|
|
154
|
+
expect(
|
|
155
|
+
() => service.getUser('999'),
|
|
156
|
+
throwsA(isA<NotFoundException>()),
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Testing Async Code
|
|
164
|
+
|
|
165
|
+
```dart
|
|
166
|
+
test('should fetch data asynchronously', () async {
|
|
167
|
+
final data = await fetchData();
|
|
168
|
+
expect(data, isNotNull);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('should complete within timeout', () async {
|
|
172
|
+
await expectLater(
|
|
173
|
+
slowOperation().timeout(const Duration(seconds: 2)),
|
|
174
|
+
completes,
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('should handle errors', () {
|
|
179
|
+
expect(
|
|
180
|
+
failingOperation(),
|
|
181
|
+
throwsA(isA<NetworkException>()),
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Testing Streams
|
|
187
|
+
|
|
188
|
+
```dart
|
|
189
|
+
test('should emit values from stream', () async {
|
|
190
|
+
final stream = Stream.fromIterable([1, 2, 3]);
|
|
191
|
+
|
|
192
|
+
expect(stream, emitsInOrder([1, 2, 3]));
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('should emit and complete', () {
|
|
196
|
+
expect(
|
|
197
|
+
countStream(3),
|
|
198
|
+
emitsInOrder([
|
|
199
|
+
1,
|
|
200
|
+
2,
|
|
201
|
+
3,
|
|
202
|
+
emitsDone,
|
|
203
|
+
]),
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('should handle stream errors', () {
|
|
208
|
+
expect(
|
|
209
|
+
errorStream(),
|
|
210
|
+
emitsInOrder([
|
|
211
|
+
1,
|
|
212
|
+
emitsError(isA<Exception>()),
|
|
213
|
+
]),
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Widget Testing (Flutter)
|
|
219
|
+
|
|
220
|
+
```dart
|
|
221
|
+
import 'package:flutter_test/flutter_test.dart';
|
|
222
|
+
|
|
223
|
+
void main() {
|
|
224
|
+
testWidgets('should display user name', (WidgetTester tester) async {
|
|
225
|
+
// Arrange
|
|
226
|
+
final user = User(id: '1', name: 'Alice');
|
|
227
|
+
|
|
228
|
+
// Act
|
|
229
|
+
await tester.pumpWidget(
|
|
230
|
+
MaterialApp(home: UserProfile(user: user)),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// Assert
|
|
234
|
+
expect(find.text('Alice'), findsOneWidget);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
testWidgets('should handle button tap', (WidgetTester tester) async {
|
|
238
|
+
bool tapped = false;
|
|
239
|
+
|
|
240
|
+
await tester.pumpWidget(
|
|
241
|
+
MaterialApp(
|
|
242
|
+
home: Scaffold(
|
|
243
|
+
body: ElevatedButton(
|
|
244
|
+
onPressed: () => tapped = true,
|
|
245
|
+
child: const Text('Tap me'),
|
|
246
|
+
),
|
|
247
|
+
),
|
|
248
|
+
),
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Find and tap button
|
|
252
|
+
await tester.tap(find.text('Tap me'));
|
|
253
|
+
await tester.pump();
|
|
254
|
+
|
|
255
|
+
expect(tapped, isTrue);
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Integration Testing
|
|
261
|
+
|
|
262
|
+
```dart
|
|
263
|
+
import 'package:integration_test/integration_test.dart';
|
|
264
|
+
import 'package:flutter_test/flutter_test.dart';
|
|
265
|
+
|
|
266
|
+
void main() {
|
|
267
|
+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
|
268
|
+
|
|
269
|
+
group('Login flow', () {
|
|
270
|
+
testWidgets('should login successfully', (tester) async {
|
|
271
|
+
await tester.pumpWidget(MyApp());
|
|
272
|
+
|
|
273
|
+
// Enter credentials
|
|
274
|
+
await tester.enterText(
|
|
275
|
+
find.byKey(const Key('email_field')),
|
|
276
|
+
'test@example.com',
|
|
277
|
+
);
|
|
278
|
+
await tester.enterText(
|
|
279
|
+
find.byKey(const Key('password_field')),
|
|
280
|
+
'password123',
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
// Tap login
|
|
284
|
+
await tester.tap(find.byKey(const Key('login_button')));
|
|
285
|
+
await tester.pumpAndSettle();
|
|
286
|
+
|
|
287
|
+
// Verify navigation to home
|
|
288
|
+
expect(find.byType(HomePage), findsOneWidget);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Test Doubles
|
|
295
|
+
|
|
296
|
+
```dart
|
|
297
|
+
// Fake - working implementation (not for production)
|
|
298
|
+
class FakeUserRepository implements UserRepository {
|
|
299
|
+
final _users = <String, User>{};
|
|
300
|
+
|
|
301
|
+
@override
|
|
302
|
+
Future<User?> findById(String id) async {
|
|
303
|
+
return _users[id];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
@override
|
|
307
|
+
Future<void> save(User user) async {
|
|
308
|
+
_users[user.id] = user;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Stub - returns canned responses
|
|
313
|
+
class StubApiClient implements ApiClient {
|
|
314
|
+
@override
|
|
315
|
+
Future<Response> get(String url) async {
|
|
316
|
+
return Response(statusCode: 200, data: {'id': '1', 'name': 'Alice'});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Mock - pre-programmed with expectations (use Mockito)
|
|
321
|
+
final mock = MockUserRepository();
|
|
322
|
+
when(mock.findById('1')).thenAnswer((_) async => testUser);
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Coverage
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
# Run tests with coverage
|
|
329
|
+
dart test --coverage=coverage
|
|
330
|
+
|
|
331
|
+
# Generate HTML report
|
|
332
|
+
genhtml coverage/lcov.info -o coverage/html
|
|
333
|
+
|
|
334
|
+
# View coverage
|
|
335
|
+
open coverage/html/index.html
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Best Practices
|
|
339
|
+
|
|
340
|
+
- Use `group()` to organize related tests
|
|
341
|
+
- One test per behavior
|
|
342
|
+
- Use descriptive test names: "should [expected behavior] when [scenario]"
|
|
343
|
+
- Follow Arrange-Act-Assert pattern
|
|
344
|
+
- Test observable behavior, not implementation
|
|
345
|
+
- Use setUp/tearDown for common initialization
|
|
346
|
+
- Mock external dependencies
|
|
347
|
+
- Test edge cases and error conditions
|
|
348
|
+
- Keep tests fast and independent
|
|
349
|
+
- Use `pump()` and `pumpAndSettle()` in widget tests
|
|
350
|
+
- Verify interactions with mocks
|
|
351
|
+
- Aim for high code coverage (80%+)
|
|
352
|
+
- Run tests in CI/CD pipeline
|