Skip to content

Writing Tests

Write effective tests for Streams applications.

This guide covers everything you need to know about writing tests for Streams applications, from basic examples to advanced testing patterns.

Basic Test Structure

Your First Test

Every test extends the Streams\Testing\TestCase class:

<?php

namespace Tests;

use Streams\Core\Support\Facades\Streams;

class MyFirstTest extends \Streams\Testing\TestCase
{
    public function test_example()
    {
        $this->assertTrue(true);
    }
}

Anatomy of a Test

public function test_descriptive_name()  // 1. Test method
{
    // 2. Arrange - Set up test data
    $stream = Streams::make('films');
    
    // 3. Act - Perform the action
    $result = $stream->entries()->count();
    
    // 4. Assert - Verify the result
    $this->assertEquals(7, $result);
}

Testing Streams

Creating Entries

public function test_creates_new_film()
{
    $film = Streams::make('films')->create([
        'title' => 'New Film',
        'director' => 'New Director',
        'release_date' => '2025-01-01',
    ]);
    
    $this->assertNotNull($film->id);
    $this->assertEquals('New Film', $film->title);
}

Reading Entries

public function test_finds_film_by_title()
{
    $film = Streams::entries('films')
        ->where('title', 'A New Hope')
        ->first();
    
    $this->assertNotNull($film);
    $this->assertEquals('George Lucas', $film->director);
}

Updating Entries

public function test_updates_film_director()
{
    $film = Streams::entries('films')
        ->where('title', 'A New Hope')
        ->first();
    
    $film->update(['director' => 'Updated Director']);
    
    $updated = Streams::entries('films')
        ->where('title', 'A New Hope')
        ->first();
    
    $this->assertEquals('Updated Director', $updated->director);
}

Deleting Entries

public function test_deletes_film()
{
    $initialCount = Streams::entries('films')->count();
    
    $film = Streams::entries('films')->first();
    $film->delete();
    
    $finalCount = Streams::entries('films')->count();
    
    $this->assertEquals($initialCount - 1, $finalCount);
}

Testing Queries

Simple Where Clauses

public function test_filters_by_director()
{
    $films = Streams::entries('films')
        ->where('director', 'George Lucas')
        ->get();
    
    $this->assertGreaterThan(0, $films->count());
    
    foreach ($films as $film) {
        $this->assertEquals('George Lucas', $film->director);
    }
}

Complex Queries

public function test_complex_film_query()
{
    $films = Streams::entries('films')
        ->where('director', 'George Lucas')
        ->where('release_date', '>=', '1977-01-01')
        ->orderBy('release_date')
        ->limit(3)
        ->get();
    
    $this->assertLessThanOrEqual(3, $films->count());
}

Using Like Operator

public function test_searches_film_titles()
{
    $films = Streams::entries('films')
        ->where('title', 'like', '%Empire%')
        ->get();
    
    $this->assertGreaterThan(0, $films->count());
}

Counting Results

public function test_counts_films_by_director()
{
    $count = Streams::entries('films')
        ->where('director', 'George Lucas')
        ->count();
    
    $this->assertGreaterThan(0, $count);
}

Testing Stream Definitions

Validating Stream Configuration

public function test_films_stream_configuration()
{
    $stream = Streams::make('films');
    
    $this->assertEquals('Films', $stream->name);
    $this->assertEquals('episode_id', $stream->config['key_name']);
}

Testing Field Definitions

public function test_films_has_required_fields()
{
    $stream = Streams::make('films');
    $fields = $stream->fields;
    
    $this->assertArrayHasKey('title', $fields);
    $this->assertArrayHasKey('director', $fields);
    $this->assertArrayHasKey('release_date', $fields);
}

Validating Field Types

public function test_field_types()
{
    $stream = Streams::make('films');
    
    $this->assertEquals('integer', $stream->fields['episode_id']->type);
    $this->assertEquals('string', $stream->fields['title']->type);
    $this->assertEquals('datetime', $stream->fields['release_date']->type);
}

Testing Relationships

One-to-Many Relationships

public function test_planet_has_residents()
{
    $tatooine = Streams::entries('planets')
        ->where('name', 'Tatooine')
        ->first();
    
    $residents = Streams::entries('people')
        ->where('homeworld', $tatooine->name)
        ->get();
    
    $this->assertGreaterThan(0, $residents->count());
}

Many-to-Many Relationships

public function test_character_appears_in_films()
{
    $luke = Streams::entries('people')
        ->where('name', 'Luke Skywalker')
        ->first();
    
    // Assuming a films relationship
    if (isset($luke->films)) {
        $this->assertGreaterThan(0, count($luke->films));
    }
}

Using Setup and Teardown

Setup Method

Run code before each test:

protected function setUp(): void
{
    parent::setUp();
    
    // Create test data that all tests need
    $this->testFilm = Streams::make('films')->create([
        'title' => 'Test Film',
        'director' => 'Test Director',
    ]);
}

Teardown Method

The parent TestCase handles data restoration automatically. Add custom cleanup if needed:

protected function tearDown(): void
{
    // Custom cleanup here
    
    parent::tearDown(); // This restores test data
}

Data Providers

Test the same logic with different data:

/**
 * @dataProvider filmDirectorProvider
 */
public function test_finds_films_by_director($director, $expectedMinimum)
{
    $films = Streams::entries('films')
        ->where('director', $director)
        ->get();
    
    $this->assertGreaterThanOrEqual($expectedMinimum, $films->count());
}

public static function filmDirectorProvider()
{
    return [
        'George Lucas' => ['George Lucas', 1],
        'Irvin Kershner' => ['Irvin Kershner', 1],
        'Richard Marquand' => ['Richard Marquand', 1],
    ];
}

Testing Exceptions

Expecting Exceptions

public function test_throws_exception_for_invalid_stream()
{
    $this->expectException(\Exception::class);
    
    Streams::make('nonexistent_stream');
}

Testing Validation Errors

public function test_validates_required_fields()
{
    $this->expectException(\Illuminate\Validation\ValidationException::class);
    
    Streams::make('films')->create([
        // Missing required 'title' field
        'director' => 'Test Director',
    ]);
}

Common Assertions

Equality Assertions

$this->assertEquals($expected, $actual);
$this->assertNotEquals($unexpected, $actual);
$this->assertSame($expected, $actual); // Strict comparison

Boolean Assertions

$this->assertTrue($condition);
$this->assertFalse($condition);
$this->assertNull($value);
$this->assertNotNull($value);

Collection Assertions

$this->assertCount($expectedCount, $collection);
$this->assertEmpty($collection);
$this->assertNotEmpty($collection);
$this->assertContains($needle, $collection);

Array Assertions

$this->assertArrayHasKey($key, $array);
$this->assertArrayNotHasKey($key, $array);
$this->assertIsArray($value);

String Assertions

$this->assertStringContainsString($needle, $haystack);
$this->assertStringStartsWith($prefix, $string);
$this->assertStringEndsWith($suffix, $string);
$this->assertMatchesRegularExpression($pattern, $string);

Numeric Assertions

$this->assertGreaterThan($expected, $actual);
$this->assertGreaterThanOrEqual($expected, $actual);
$this->assertLessThan($expected, $actual);
$this->assertLessThanOrEqual($expected, $actual);

Testing Best Practices

1. One Assertion Per Test (When Possible)

// Good
public function test_film_has_title()
{
    $film = Streams::entries('films')->first();
    $this->assertNotEmpty($film->title);
}

public function test_film_has_director()
{
    $film = Streams::entries('films')->first();
    $this->assertNotEmpty($film->director);
}

2. Use Descriptive Test Names

// Bad
public function test_film() { }

// Good
public function test_creates_film_with_all_required_fields() { }

3. Follow the AAA Pattern

public function test_updates_film_release_date()
{
    // Arrange
    $film = Streams::entries('films')->first();
    $newDate = '2025-12-25';
    
    // Act
    $film->update(['release_date' => $newDate]);
    
    // Assert
    $this->assertEquals($newDate, $film->fresh()->release_date);
}

4. Test Edge Cases

public function test_handles_empty_query_results()
{
    $films = Streams::entries('films')
        ->where('director', 'Nonexistent Director')
        ->get();
    
    $this->assertCount(0, $films);
}

5. Keep Tests Independent

Each test should work in isolation:

public function test_first_test()
{
    $film = Streams::make('films')->create(['title' => 'Test']);
    $this->assertNotNull($film);
}

public function test_second_test()
{
    // Don't rely on data from test_first_test
    $count = Streams::entries('films')->count();
    $this->assertEquals(7, $count); // Original test data
}

Debugging Tests

Using dump() and dd()

public function test_debugging_example()
{
    $films = Streams::entries('films')->get();
    
    dump($films); // Output without stopping
    
    // dd($films); // Dump and die
    
    $this->assertNotEmpty($films);
}

Verbose Output

Run tests with verbose output:

vendor/bin/phpunit --testdox --verbose

Running Single Tests

Test specific methods:

vendor/bin/phpunit --filter test_specific_method

Next Steps