# Testing Guide for QuitSure Laravel Application

## Overview

This document provides essential information about the test suite for the QuitSure Laravel application. The test suite uses **Pest PHP** testing framework and follows modern testing best practices for Laravel applications.

## Summary

### Test Coverage Statistics
- **Total Tests**: 204 tests with 617+ assertions
- **Feature Tests**: 179 tests (API endpoint testing)
- **Unit Tests**: 25 tests (Service layer testing)
- **Pass Rate**: 100%

### APIs Tested
The following API endpoints have comprehensive test coverage:
- User Authentication (Email Login, SSO, Social Login)
- App Configuration (`getAppConfiguration`)
- Download Tracking (`newDownload`)
- Notification Management (`notificationStatus`)
- Connectivity Check (`internetState`)
- App Version Check (`appUpdateCheck`)
- Exercise Updates (`UpdateExercise`)
- Program Listings (`getProgramList`)
- Chapter Status Updates (`upadateChapterStatus`)

### Test Types
1. **Feature Tests** (`tests/Feature/`): Full HTTP request/response testing of API endpoints
2. **Unit Tests** (`tests/Unit/`): Isolated testing of service classes and business logic

---

## Why We Use Mocking

### Purpose of Mocking
Mocking is essential for writing fast, reliable, and isolated tests. Here's why:

1. **Isolation**: Tests should verify the logic of the code under test, not the behavior of its dependencies
2. **Speed**: Mocking eliminates slow operations like database queries, API calls, and file I/O
3. **Reliability**: Tests won't fail due to external factors (network issues, database state, third-party APIs)
4. **Control**: We can simulate edge cases and error conditions that are hard to reproduce with real dependencies
5. **Determinism**: Tests produce consistent results every time they run

### What We Mock
- **Database Queries**: Using Mockery to mock Query classes
- **External Services**: PostmarkService (email), GeoLocationService, MetaCapiService, etc.
- **Laravel Facades**: DB, Log, Config when necessary
- **Third-party APIs**: Firebase, AWS S3, Stripe, etc.

### Example
```php
// Mock the PostmarkService
$postmarkService = Mockery::mock(PostmarkService::class);
$postmarkService->shouldReceive('sendPredefinedTemplate')
    ->once()
    ->andReturn(true);

// Now the test can verify OTP logic without actually sending emails
```

---

## Best Practices

### 1. Test Structure (AAA Pattern)
Every test follows the **Arrange-Act-Assert** pattern:

```php
it('generates OTP successfully for valid email', function () {
    // Arrange - Set up test data and mocks
    $postData = ['vEmail' => 'test@example.com'];
    $this->loginQuery->shouldReceive('expirePreviousOTP')->andReturn(true);

    // Act - Execute the code being tested
    $result = $this->emailLoginService->generateOTP($postData);

    // Assert - Verify the outcome
    expect($result['status'])->toBe(true);
});
```

### 2. Descriptive Test Names
Use natural language to describe what the test verifies:
- ✅ `it('returns error when email is invalid')`
- ✅ `it('successfully updates notification status for authenticated user')`
- ❌ `testEmailValidation()`
- ❌ `test_function_1()`

### 3. Test Independence
Each test should:
- Run independently of other tests
- Not depend on execution order
- Clean up after itself (handled by `beforeEach` and `afterEach`)
- Not share state with other tests

### 4. Comprehensive Coverage
Test the following scenarios:
- **Happy Path**: Valid inputs produce expected outputs
- **Error Cases**: Invalid inputs produce appropriate errors
- **Edge Cases**: Boundary conditions, null values, empty strings
- **Business Rules**: Special logic (e.g., special OTP for test email)

### 5. Clear Assertions
Use specific, meaningful assertions:
```php
// ✅ Good - Multiple specific assertions
expect($result['status'])->toBe(true)
    ->and($result['code'])->toBe(200)
    ->and($result['response']['message'])->toBe('Success');

// ❌ Bad - Vague assertion
expect($result)->not->toBeNull();
```

### 6. Mock Setup in beforeEach
Common mocks are initialized in `beforeEach()` to avoid repetition:

```php
beforeEach(function () {
    // Mock facades
    DB::shouldReceive('beginTransaction')->andReturn(null);
    Log::shouldReceive('error')->andReturn(null);

    // Mock dependencies
    $this->postmarkService = Mockery::mock(PostmarkService::class);
    $this->loginQuery = Mockery::mock(LoginQuery::class);

    // Inject mocked dependencies
    $this->emailLoginService = new EmailLoginService(
        $this->loginQuery,
        $this->postmarkService,
        // ... other dependencies
    );
});
```

---

## Important Testing Concepts

### Feature Tests vs Unit Tests

**Feature Tests** (`tests/Feature/`):
- Test complete HTTP request/response cycles
- Use Laravel's TestCase with database transactions
- Verify API contracts and integration between layers
- Run slower but provide end-to-end confidence

**Unit Tests** (`tests/Unit/`):
- Test individual classes or methods in isolation
- Use extensive mocking to eliminate dependencies
- Run very fast (no database, no I/O)
- Verify business logic and edge cases

### Laravel TestCase Configuration

**Critical**: The `tests/Pest.php` file must configure TestCase for both Feature and Unit tests:

```php
// Enable Laravel's TestCase for Feature tests
pest()->extend(Tests\TestCase::class)
    ->in('Feature');

// Enable Laravel's TestCase for Unit tests (required for config(), etc.)
pest()->extend(Tests\TestCase::class)
    ->in('Unit');
```

**Why this matters**: Without extending `Tests\TestCase`, unit tests won't have access to:
- Laravel's `config()` helper
- Database transactions
- Facade mocking
- Other Laravel testing utilities

### Database Testing Strategy

**Feature Tests**: Use real database with transactions
```php
use Illuminate\Foundation\Testing\RefreshDatabase;

// Database changes are rolled back after each test
```

**Unit Tests**: Mock all database queries
```php
DB::shouldReceive('beginTransaction')->andReturn(null);
DB::shouldReceive('commit')->andReturn(null);

// No actual database queries are executed
```

### Mocking with Mockery

```php
// Create a mock
$mock = Mockery::mock(SomeClass::class);

// Set expectations
$mock->shouldReceive('methodName')
    ->once()                    // Called exactly once
    ->with('expectedArg')       // With specific argument
    ->andReturn('result');      // Returns this value

// Alternative: allow any calls without strict expectations
$mock->allows('methodName')->andReturn('result');

// Clean up mocks after each test
afterEach(function () {
    Mockery::close();
});
```

---

## Running Tests

### Basic Commands

```bash
# Run all tests
vendor/bin/pest --env=testing

OR

php artisan test --env=testing


# Run all tests with coverage (if configured)
vendor/bin/pest --coverage --env=testing

# Run only Feature tests
vendor/bin/pest tests/Feature --env=testing

# Run only Unit tests
vendor/bin/pest tests/Unit --env=testing

# Run a specific test file
vendor/bin/pest tests/Unit/EmailLoginServiceTest.php --env=testing

# Run tests matching a filter
vendor/bin/pest --filter="generates OTP" --env=testing

# Run tests in parallel (faster)
vendor/bin/pest --parallel --env=testing
```

---

## Test Organization

### File Structure
```
tests/
├── Feature/
│   └── Api/
│       ├── Auth/
│       │   ├── EmailLoginApiTest.php
│       │   ├── SocialLoginApiTest.php
│       │   └── SSOLoginApiTest.php
│       ├── Chapter/
│       │   └── GetChapterStatusApiTest.php
│       ├── Program/
│       │   └── GetProgramListApiTest.php
│       ├── UserActivity/
│       │   ├── AppUpdateCheckApiTest.php
│       │   ├── UpdateExerciseApiTest.php
│       │   └── UpadateChapterStatusApiTest.php
│       ├── GetAppConfigurationApiTest.php
│       ├── NewDownloadApiTest.php
│       └── ...
├── Unit/
│   └── Services/
│       └── Auth/
│           ├── EmailLoginServiceTest.php
│           ├── SSOLoginServiceTest.php
│           └── SocialLoginServiceTest.php
├── Pest.php          # Pest configuration
└── TestCase.php      # Base test case class
```

### Naming Conventions
- **Test Files**: `*Test.php` (e.g., `EmailLoginServiceTest.php`)
- **Test Methods**: Use Pest's `it()` function with descriptive text
- **Describe Blocks**: Group related tests using `describe()`

```php
describe('EmailLoginService - generateOTP Method', function () {
    it('generates OTP successfully for valid email', function () {
        // ...
    });

    it('returns error for invalid email format', function () {
        // ...
    });
});
```

---

## Debugging Failed Tests

### Common Issues and Solutions

**1. Config Not Available in Unit Tests**
```
Error: Target class [config] does not exist
```
**Solution**: Ensure `tests/Pest.php` extends `Tests\TestCase` for Unit tests

**2. Mock Not Called**
```
Error: Method shouldReceive() should be called exactly 1 times but called 0 times
```
**Solution**: The code path doesn't reach the mocked method. Check validation logic or add debugging

**3. Database Transaction Issues**
```
Error: SQLSTATE connection error
```
**Solution**: Feature tests need database access. Unit tests should mock all queries

**4. Unexpected Response Structure**
```
Error: Failed asserting that array has key 'result'
```
**Solution**: Dump the actual response to see structure: `dump($result);`

### Debugging Tips

```php
// Add dumps to see actual values
it('tests something', function () {
    $result = $service->doSomething();
    dump($result);  // See what you actually got

    expect($result['status'])->toBe(true);
});

// Capture Log::error() messages
beforeEach(function () {
    $this->errorMessages = [];
    Log::shouldReceive('error')->andReturnUsing(function ($msg) {
        $this->errorMessages[] = $msg;
    });
});

// Then in your test
dump($this->errorMessages);  // See what errors were logged
```

---

## Continuous Integration

### Pre-Commit Checklist
Before committing code:
1. ✅ Run all tests: `vendor/bin/pest`
2. ✅ Check code style: `vendor/bin/pint`
3. ✅ Run static analysis: `vendor/bin/phpstan analyse`
4. ✅ Ensure no failing tests

---

## Writing New Tests

### Checklist for New Feature Tests
- [ ] Test happy path (valid request → success response)
- [ ] Test authentication/authorization
- [ ] Test required field validation
- [ ] Test optional field handling
- [ ] Test error responses
- [ ] Test edge cases (empty strings, nulls, special characters)
- [ ] Verify response structure matches API documentation

### Checklist for New Unit Tests
- [ ] Mock all external dependencies
- [ ] Test public method behavior
- [ ] Test error handling and exceptions
- [ ] Test boundary conditions
- [ ] Verify correct calls to dependencies
- [ ] Test business logic thoroughly
- [ ] Keep tests fast (< 100ms per test)

---

## Additional Resources

### Documentation
- [Pest PHP Documentation](https://pestphp.com/docs)
- [Laravel Testing Documentation](https://laravel.com/docs/testing)
- [Mockery Documentation](http://docs.mockery.io/)

### Key Testing Principles
1. **FIRST Principles**: Fast, Independent, Repeatable, Self-validating, Timely
2. **Test Behavior, Not Implementation**: Tests should verify what code does, not how
3. **One Assert Per Concept**: Each test should verify one specific behavior
4. **Given-When-Then**: Arrange-Act-Assert expressed as Given-When-Then in BDD
5. **Test-Driven Development (TDD)**: Write tests before writing code when possible

---

## Maintenance

### Updating Tests
When modifying existing code:
1. Run affected tests to verify they still pass
2. Update test expectations if behavior intentionally changed
3. Add new tests for new functionality
4. Remove obsolete tests for removed features

### Test Code Quality
- Keep tests simple and readable
- Avoid complex logic in tests
- Refactor repeated setup into helper methods
- Document complex test scenarios
- Review test code as rigorously as production code

---

## Questions or Issues?

If tests are failing or you need help:
1. Check this documentation first
2. Review the specific test file for context
3. Look at similar passing tests for patterns
4. Use debugging techniques outlined above
5. Consult the team
