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