# How to Write Integration Tests for Palmier Pro: A Swift Testing Guide

> Learn to write integration tests for Palmier Pro with Swift. Combine fixtures and services to verify end-to-end video export functionality on macOS.

- Repository: [Palmier/palmier-pro](https://github.com/palmier-io/palmier-pro)
- Tags: how-to-guide
- Published: 2026-06-21

---

**Write integration tests for Palmier Pro by combining `ImageVideoGenerator` fixtures, `MediaManifest` construction, and `ExportService` calls to verify end-to-end video export functionality on macOS.**

Palmier Pro’s video editing pipeline relies on tight integration between timeline resolution, media handling, and AVFoundation-based export. Writing robust integration tests ensures that these subsystems cooperate correctly when processing real video assets. This guide walks through the architecture, implementation patterns, and concrete code examples found in the `palmier-io/palmier-pro` repository.

## Understanding the Integration Test Architecture

Integration tests in Palmier Pro exercise four distinct layers in a single asynchronous run. Unlike unit tests that mock `AVAssetExportSession`, these tests use real video generation and file I/O to catch codec-specific edge cases and export corruption issues.

### Media Fixture Generation

The test suite uses `ImageVideoGenerator.blackVideo(size:)` to create deterministic video assets on-the-fly. This async method returns a temporary URL to a generated black-screen clip, providing a consistent input for the export pipeline without bundling large binary files in the repository.

### Project Model and Timeline Resolution

Tests construct a `MediaManifest` to register fixture assets with unique IDs, then instantiate a `MediaResolver` to map those IDs to filesystem URLs. The timeline model (`Timeline`, `Clip`) describes the sequence of media, while `timeline.width` and `timeline.height` define the render canvas dimensions.

### Export Service Orchestration

`ExportService.export(timeline:resolver:format:resolution:outputURL:)` coordinates the conversion of the timeline into an AV composition, applies text layers, and executes an `AVAssetExportSession`. The service exposes public state (`progress`, `error`) for test assertions.

### Verification with AVFoundation

After export completion, tests load the output file using `AVURLAsset(url:)` and async `load(_:)` calls to verify duration, track presence, and codec compliance. This validates that the exported file is playable and structurally sound, not merely that the export method returned without error.

## Step-by-Step Blueprint for Integration Tests

Follow this sequence when adding new integration tests to `Tests/PalmierProTests/Export/`:

1. **Generate a fixture** – Call `ImageVideoGenerator.blackVideo(size:)` with your target render dimensions (e.g., 320 × 180).
2. **Build a manifest** – Create a `MediaManifest` and append a `MediaManifestEntry` pointing to the fixture’s absolute path.
3. **Create a resolver** – Instantiate `MediaResolver` with closures returning the manifest and an optional project URL.
4. **Construct the timeline** – Use helper functions from [`Tests/PalmierProTests/Fixtures.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Tests/PalmierProTests/Fixtures.swift) to create `Clip` objects and assemble a `Timeline` with matching width/height.
5. **Set the output destination** – Generate a unique temporary URL in `NSTemporaryDirectory()` with a `.mp4` extension.
6. **Execute the export** – Await `ExportService().export(...)` with your chosen `ExportFormat` (e.g., `.h264`) and `ExportResolution` (e.g., `.r720p`).
7. **Assert service state** – Verify `svc.error == nil`, `svc.progress == 1.0`, and that the output file exists.
8. **Validate the asset** – Load the exported file with `AVURLAsset` and check that `duration.seconds` matches expectations and video tracks are present.
9. **Clean up** – Remove the temporary file in a `defer` block to prevent disk pollution.

## Complete Code Example: Export Round-Trip Test

The reference implementation in [`ExportServiceRoundTripTests.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ExportServiceRoundTripTests.swift) demonstrates a full integration test using the Swift Testing framework (`@Suite` and `@Test` attributes):

```swift
// Tests/PalmierProTests/Export/ExportServiceRoundTripTests.swift
@Suite("ExportService — round‑trip")
@MainActor
struct ExportServiceRoundTripTests {

    @Test func h264ExportProducesPlayableMp4ContainingVideoTrack() async throws {
        // 1️⃣  Create a 320 × 180 black‑video fixture.
        let renderSize = CGSize(width: 320, height: 180)
        let blackURL   = try await ImageVideoGenerator.blackVideo(size: renderSize)

        // 2️⃣  Register the fixture in a manifest.
        let mediaRef = "black-fixture"
        var manifest = MediaManifest()
        manifest.entries = [
            MediaManifestEntry(id: mediaRef, name: "black",
                              type: .video,
                              source: .external(absolutePath: blackURL.path),
                              duration: 5.0)
        ]

        // 3️⃣  Resolve URLs via a resolver.
        let resolver = MediaResolver(manifest: { manifest }, projectURL: { nil })

        // 4️⃣  Build a one‑second clip on a timeline.
        let clip = Fixtures.clip(id: "c1", mediaRef: mediaRef, start: 0, duration: 30)
        var timeline = Fixtures.timeline(tracks: [Fixtures.videoTrack(clips: [clip])])
        timeline.width  = Int(renderSize.width)
        timeline.height = Int(renderSize.height)

        // 5️⃣  Export to a temporary file.
        let outURL = URL(fileURLWithPath: NSTemporaryDirectory())
            .appendingPathComponent("export-\(UUID().uuidString).mp4")
        defer { try? FileManager.default.removeItem(at: outURL) }

        let svc = ExportService()
        await svc.export(timeline: timeline,
                        resolver: resolver,
                        format: .h264,
                        resolution: .r720p,
                        outputURL: outURL)

        // 6️⃣  Check ExportService state.
        #expect(svc.error == nil, "export reported error: \(svc.error ?? "")")
        #expect(svc.progress == 1.0)
        #expect(FileManager.default.fileExists(atPath: outURL.path))

        // 7️⃣  Load the exported file and verify it is a real video.
        let asset    = AVURLAsset(url: outURL)
        let duration = try await asset.load(.duration)
        #expect(duration.seconds > 0)
        #expect(abs(duration.seconds - 1.0) < 0.5,
                "expected ~1 s exported, got \(duration.seconds)s")

        let videoTracks = try await asset.loadTracks(withMediaType: .video)
        #expect(!videoTracks.isEmpty, "exported file has no video tracks")
    }
}

```

This test verifies that the entire pipeline—from media generation through `ExportService` to final file output—produces a valid video file with the expected duration and track structure.

## Testing Advanced Scenarios

### Transform Keyframes and Animation

Test complex timeline behavior by attaching `KeyframeTrack` objects to clips. This example from [`ExportServiceRoundTripTests.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ExportServiceRoundTripTests.swift) validates that keyframes starting at clip offset zero do not crash the export:

```swift
@Test func exportSurvivesTransformKeyframeAtClipOffsetZero() async throws {
    // Generate fixture and build manifest (steps omitted for brevity)
    
    // Apply scale and position keyframes
    var clip = Fixtures.clip(id: "c1", mediaRef: mediaRef, start: 0, duration: 30)
    clip.scaleTrack = KeyframeTrack(keyframes: [
        Keyframe(frame: 0, value: AnimPair(a: 1.0, b: 1.0), interpolationOut: .linear),
        Keyframe(frame: 30, value: AnimPair(a: 1.08, b: 1.08), interpolationOut: .linear),
    ])
    clip.positionTrack = KeyframeTrack(keyframes: [
        Keyframe(frame: 0, value: AnimPair(a: 0, b: 0), interpolationOut: .linear),
        Keyframe(frame: 30, value: AnimPair(a: -0.04, b: 0), interpolationOut: .linear),
    ])
    
    // Proceed with timeline assembly and export...
}

```

### Export Resolution Logic

Verify that `ExportResolution` correctly calculates output dimensions for different aspect ratios in [`ExportResolutionTests.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ExportResolutionTests.swift):

```swift
// Tests/PalmierProTests/Export/ExportResolutionTests.swift
@Suite("ExportResolution.renderSize")
struct ExportResolutionTests {

    @Test func landscape720pDownscalesShortSideTo720() {
        let size = ExportResolution.r720p.renderSize(for: CGSize(width: 1920, height: 1080))
        #expect(size == CGSize(width: 1280, height: 720))
    }
}

```

## Key Source Files for Integration Testing

When writing integration tests for Palmier Pro, reference these core files:

- **[`Sources/PalmierPro/Export/ExportService.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Export/ExportService.swift)** – Central orchestration of the AVFoundation export pipeline and `AVAssetExportSession` management.
- **[`Sources/PalmierPro/Models/MediaManifest.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Models/MediaManifest.swift)** – Defines `MediaManifest` and `MediaManifestEntry` for registering external assets.
- **[`Sources/PalmierPro/Models/Timeline.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Models/Timeline.swift)** – Contains `Timeline` and `Clip` structs describing the video sequence and canvas dimensions.
- **[`Sources/PalmierPro/Preview/ImageVideoGenerator.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Preview/ImageVideoGenerator.swift)** – Provides `blackVideo(size:)` for generating test fixtures.
- **[`Tests/PalmierProTests/Fixtures.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Tests/PalmierProTests/Fixtures.swift)** – Helper functions (`Fixtures.clip`, `Fixtures.timeline`) for rapid test construction.
- **[`Tests/PalmierProTests/Export/ExportServiceRoundTripTests.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Tests/PalmierProTests/Export/ExportServiceRoundTripTests.swift)** – Reference implementation showing the full integration test pattern.
- **[`Tests/PalmierProTests/Export/ExportResolutionTests.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Tests/PalmierProTests/Export/ExportResolutionTests.swift)** – Examples of testing resolution mapping logic in isolation.

## Summary

Writing integration tests for Palmier Pro requires coordinating **media generation**, **timeline modeling**, and **AVFoundation export** verification in a single async test function. By using `ImageVideoGenerator` for deterministic fixtures and `AVURLAsset` for post-export validation, you catch real-world bugs that unit tests with mocked dependencies cannot detect.

- Use the Swift Testing framework’s `@Suite` and `@Test` attributes with `@MainActor` for UI-related tests
- Always verify exported files with `AVURLAsset` to confirm duration and track presence
- Leverage [`Fixtures.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Fixtures.swift) helpers to reduce boilerplate when building timelines
- Test edge cases like keyframe offsets and resolution downscaling to prevent regression

## Frequently Asked Questions

### What testing framework does Palmier Pro use for integration tests?

Palmier Pro uses the Swift 5.9-compatible **Testing** framework, utilizing `@Suite` and `@Test` attributes instead of XCTest. This framework supports async/await syntax natively, making it ideal for testing `ExportService` methods that call `AVAssetExportSession` asynchronously.

### Why generate video fixtures instead of using bundled test files?

The `ImageVideoGenerator.blackVideo(size:)` method creates deterministic assets on-demand, keeping the repository size small while ensuring tests run with fresh, uncorrupted files. Generated fixtures also allow testing specific resolutions (e.g., 320 × 180) without maintaining multiple binary assets.

### How do I test different export formats like HEVC or ProRes?

Pass the desired format to the `ExportService.export(...)` method’s `format` parameter. For example, use `format: .h265` instead of `.h264`, then verify codec-specific properties by inspecting `AVAssetTrack` format descriptions after export completion.

### Should integration tests clean up temporary files?

Yes. Always wrap temporary file removal in a `defer` block immediately after creating the output URL, as shown in the [`ExportServiceRoundTripTests.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ExportServiceRoundTripTests.swift) example. This prevents accumulation of test artifacts in `NSTemporaryDirectory()` across test runs.