Testing
Mydia uses ExUnit for unit and integration testing with comprehensive LiveView test support.
Running Tests
All Tests
Specific File
Specific Test
Failed Tests Only
Test Types
Unit Tests
Test individual modules and functions:
defmodule Mydia.LibrariesTest do
use Mydia.DataCase
alias Mydia.Libraries
describe "list_libraries/0" do
test "returns all libraries" do
library = library_fixture()
assert Libraries.list_libraries() == [library]
end
end
end
Integration Tests
Test multiple components together:
defmodule Mydia.Downloads.PipelineTest do
use Mydia.DataCase
test "download pipeline processes release correctly" do
# Setup
movie = movie_fixture()
release = release_fixture(movie)
# Execute
{:ok, download} = Downloads.start_download(release)
# Verify
assert download.status == :downloading
end
end
LiveView Tests
Test Phoenix LiveView components:
defmodule MydiaWeb.LibraryLiveTest do
use MydiaWeb.ConnCase
import Phoenix.LiveViewTest
describe "index" do
test "lists all libraries", %{conn: conn} do
library = library_fixture()
{:ok, view, html} = live(conn, ~p"/libraries")
assert html =~ library.name
end
test "creates new library", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/libraries")
view
|> form("#library-form", library: %{name: "Movies"})
|> render_submit()
assert has_element?(view, "#library-list", "Movies")
end
end
end
Test Helpers
Data Case
For tests that need database access:
Provides:
- Database transactions (automatic rollback)
- Fixture functions
- Ecto helpers
Conn Case
For web tests:
Provides:
- Connection setup
- Authentication helpers
- LiveView testing
Fixtures
Create test data with fixtures:
defmodule Mydia.LibrariesFixtures do
def library_fixture(attrs \\ %{}) do
{:ok, library} =
attrs
|> Enum.into(%{
name: "Test Library",
path: "/test/path",
type: :movies
})
|> Mydia.Libraries.create_library()
library
end
end
Test Patterns
Testing LiveView Interactions
test "user can search for media", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/search")
# Fill in search form
view
|> element("#search-form")
|> render_submit(%{query: "Matrix"})
# Verify results appear
assert has_element?(view, "#search-results")
assert has_element?(view, ".result-item", "The Matrix")
end
Testing Form Validation
test "shows validation errors", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/libraries/new")
view
|> form("#library-form", library: %{name: ""})
|> render_change()
assert has_element?(view, ".error", "can't be blank")
end
Testing Flash Messages
test "shows success message", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/libraries")
view
|> form("#library-form", library: %{name: "New Library"})
|> render_submit()
assert render(view) =~ "Library created successfully"
end
Test Configuration
Test Database
Tests use a separate database that's automatically created and migrated.
Async Tests
Enable async for non-database tests:
Tags
Skip or focus tests with tags:
Run with:
Code Coverage
Generate coverage report:
Best Practices
- Test behavior, not implementation - Focus on what, not how
- One assertion per test - When practical
- Clear test names - Describe the scenario
- Use fixtures - Keep setup DRY
- Deterministic tests - No random failures
Continuous Integration
Tests run automatically on:
- Pull requests
- Commits to main
CI runs:
- Code compilation (warnings as errors)
- Formatting checks
- Credo static analysis
- Full test suite
- Docker build verification
Next Steps
- E2E Testing - Browser-based testing with Playwright
- Development Setup - Local environment setup