Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Running Tests
Comment thread
tujii marked this conversation as resolved.
Outdated

This directory contains unit tests for the AngryRaphi Flutter application.

## Prerequisites

Before running tests, you need to generate mock files for the tests. The tests use Mockito for mocking dependencies.

## Generating Mock Files

Run the following command from the project root to generate mock files:

```bash
flutter pub run build_runner build --delete-conflicting-outputs
```

Or use the watch mode for continuous generation during development:

```bash
flutter pub run build_runner watch --delete-conflicting-outputs
```

This will generate `*.mocks.dart` files next to each test file that uses `@GenerateMocks` annotations.

## Running Tests

Once mocks are generated, you can run tests using:

### Run all tests
```bash
flutter test
```

### Run a specific test file
```bash
flutter test test/features/authentication/presentation/bloc/auth_bloc_test.dart
```

### Run tests with coverage
```bash
flutter test --coverage
```

## Test Structure

The test directory mirrors the `lib` directory structure:

- `test/core/` - Tests for core functionality (widgets, utils, network)
- `test/features/` - Tests for feature-specific code (blocs, repositories, pages)
- `test/services/` - Tests for services
- `test/shared/` - Tests for shared widgets and utilities

## Test Categories

### Widget Tests
- `test/core/widgets/` - Core widget tests
- `test/shared/widgets/` - Shared widget tests
- Tests for custom widgets and UI components

### Bloc Tests
- `test/features/*/presentation/bloc/` - BLoC tests for each feature
- Uses `bloc_test` package for testing BLoC state changes

### Repository Tests
- `test/features/*/data/repositories/` - Repository implementation tests
- Tests data layer logic and error handling

### Service Tests
- `test/services/` - Service layer tests
- Tests business logic and external service interactions

### Page Tests
- `test/features/*/presentation/pages/` - Page widget tests
- Tests for complete page widgets and their interactions

## Test Coverage

To view test coverage:

1. Generate coverage:
```bash
flutter test --coverage
```

2. View coverage in browser (requires `lcov` tool):
```bash
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html
```

## Writing New Tests

When adding new tests:

1. Follow the existing test structure
2. Add `@GenerateMocks` annotation for dependencies you want to mock
3. Generate mocks using build_runner
4. Write comprehensive test cases covering:
- Happy path scenarios
- Error cases
- Edge cases
- State changes (for BLoCs)

## Common Issues

### Mock files not found
Run `flutter pub run build_runner build --delete-conflicting-outputs` to generate mock files.

### Test failures due to Firebase
Some tests may require Firebase initialization. Mock Firebase dependencies appropriately.

### Asset loading errors
Widget tests that load assets may need additional setup. Use `TestWidgetsFlutterBinding` for widget tests.
131 changes: 131 additions & 0 deletions test/core/network/network_info_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:angry_raphi/core/network/network_info.dart';

@GenerateMocks([Connectivity])
import 'network_info_test.mocks.dart';

void main() {
late NetworkInfoImpl networkInfo;
late MockConnectivity mockConnectivity;

setUp(() {
mockConnectivity = MockConnectivity();
networkInfo = NetworkInfoImpl(mockConnectivity);
});

group('NetworkInfoImpl', () {
group('isConnected', () {
test('should return true when device is connected to wifi', () async {
// arrange
when(mockConnectivity.checkConnectivity()).thenAnswer(
(_) async => [ConnectivityResult.wifi],
);

// act
final result = await networkInfo.isConnected;

// assert
expect(result, true);
verify(mockConnectivity.checkConnectivity());
});

test('should return true when device is connected to mobile data',
() async {
// arrange
when(mockConnectivity.checkConnectivity()).thenAnswer(
(_) async => [ConnectivityResult.mobile],
);

// act
final result = await networkInfo.isConnected;

// assert
expect(result, true);
verify(mockConnectivity.checkConnectivity());
});

test('should return true when device is connected to ethernet', () async {
// arrange
when(mockConnectivity.checkConnectivity()).thenAnswer(
(_) async => [ConnectivityResult.ethernet],
);

// act
final result = await networkInfo.isConnected;

// assert
expect(result, true);
});

test('should return false when device is not connected', () async {
// arrange
when(mockConnectivity.checkConnectivity()).thenAnswer(
(_) async => [ConnectivityResult.none],
);

// act
final result = await networkInfo.isConnected;

// assert
expect(result, false);
verify(mockConnectivity.checkConnectivity());
});

test('should return true when device has multiple connections', () async {
// arrange
when(mockConnectivity.checkConnectivity()).thenAnswer(
(_) async => [ConnectivityResult.wifi, ConnectivityResult.mobile],
);

// act
final result = await networkInfo.isConnected;

// assert
expect(result, true);
});

test('should return false when connectivity results contain only none',
() async {
// arrange
when(mockConnectivity.checkConnectivity()).thenAnswer(
(_) async => [ConnectivityResult.none],
);

// act
final result = await networkInfo.isConnected;

// assert
expect(result, false);
});

test('should return true when connected to VPN', () async {
// arrange
when(mockConnectivity.checkConnectivity()).thenAnswer(
(_) async => [ConnectivityResult.vpn],
);

// act
final result = await networkInfo.isConnected;

// assert
expect(result, true);
});

test('should return true when connected to bluetooth', () async {
// arrange
when(mockConnectivity.checkConnectivity()).thenAnswer(
(_) async => [ConnectivityResult.bluetooth],
);

// act
final result = await networkInfo.isConnected;

// assert
expect(result, true);
});
});
});
}
149 changes: 149 additions & 0 deletions test/core/utils/validators_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:angry_raphi/core/utils/validators.dart';

void main() {
group('Validators', () {
group('validateEmail', () {
test('should return null for valid email', () {
expect(Validators.validateEmail('test@example.com'), isNull);
expect(Validators.validateEmail('user.name@domain.co.uk'), isNull);
expect(Validators.validateEmail('john_doe@company.org'), isNull);
});

test('should return error message for empty email', () {
expect(Validators.validateEmail(''), equals('Email is required'));
expect(Validators.validateEmail(null), equals('Email is required'));
});

test('should return error message for invalid email format', () {
expect(
Validators.validateEmail('invalid'),
equals('Please enter a valid email'),
);
expect(
Validators.validateEmail('test@'),
equals('Please enter a valid email'),
);
expect(
Validators.validateEmail('@example.com'),
equals('Please enter a valid email'),
);
expect(
Validators.validateEmail('test@.com'),
equals('Please enter a valid email'),
);
});
});

group('validateName', () {
test('should return null for valid name', () {
expect(Validators.validateName('John'), isNull);
expect(Validators.validateName('Jane Doe'), isNull);
expect(Validators.validateName('A' * 50), isNull); // Max length
});

test('should return error message for empty name', () {
expect(Validators.validateName(''), equals('Name is required'));
expect(Validators.validateName(null), equals('Name is required'));
});

test('should return error message for name too short', () {
expect(
Validators.validateName('A'),
equals('Name must be at least 2 characters'),
);
});

test('should return error message for name too long', () {
expect(
Validators.validateName('A' * 51),
equals('Name cannot exceed 50 characters'),
);
});
});

group('validateDescription', () {
test('should return null for valid description', () {
expect(Validators.validateDescription('Short description'), isNull);
expect(Validators.validateDescription(''), isNull);
expect(Validators.validateDescription(null), isNull);
expect(Validators.validateDescription('A' * 500), isNull); // Max length
});

test('should return error message for description too long', () {
expect(
Validators.validateDescription('A' * 501),
equals('Description cannot exceed 500 characters'),
);
});
});

group('validateRequired', () {
test('should return null for non-empty value', () {
expect(Validators.validateRequired('Some value', 'Field'), isNull);
expect(Validators.validateRequired('123', 'Number'), isNull);
});

test('should return error message with field name for empty value', () {
expect(
Validators.validateRequired('', 'Username'),
equals('Username is required'),
);
expect(
Validators.validateRequired(null, 'Password'),
equals('Password is required'),
);
});
});

group('isValidImageType', () {
test('should return true for valid image extensions', () {
expect(Validators.isValidImageType('photo.jpg'), isTrue);
expect(Validators.isValidImageType('image.jpeg'), isTrue);
expect(Validators.isValidImageType('picture.png'), isTrue);
expect(Validators.isValidImageType('graphic.webp'), isTrue);
});

test('should return true for valid extensions regardless of case', () {
expect(Validators.isValidImageType('photo.JPG'), isTrue);
expect(Validators.isValidImageType('image.JPEG'), isTrue);
expect(Validators.isValidImageType('picture.PNG'), isTrue);
expect(Validators.isValidImageType('graphic.WEBP'), isTrue);
});

test('should return false for invalid image extensions', () {
expect(Validators.isValidImageType('document.pdf'), isFalse);
expect(Validators.isValidImageType('video.mp4'), isFalse);
expect(Validators.isValidImageType('file.txt'), isFalse);
expect(Validators.isValidImageType('archive.zip'), isFalse);
});

test('should return false for files without extension', () {
expect(Validators.isValidImageType('filename'), isFalse);
});
});

group('isValidImageSize', () {
test('should return true for valid image sizes', () {
expect(Validators.isValidImageSize(1024), isTrue); // 1KB
expect(Validators.isValidImageSize(1024 * 1024), isTrue); // 1MB
expect(Validators.isValidImageSize(5 * 1024 * 1024), isTrue); // 5MB (max)
});

test('should return false for image sizes exceeding limit', () {
expect(
Validators.isValidImageSize(5 * 1024 * 1024 + 1),
isFalse,
); // 5MB + 1 byte
expect(
Validators.isValidImageSize(10 * 1024 * 1024),
isFalse,
); // 10MB
});

test('should return true for zero size', () {
expect(Validators.isValidImageSize(0), isTrue);
});
});
});
}
Loading
Loading