How to Write Integration Tests for Palmier Pro: A Swift Testing Guide
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/:
- Generate a fixture – Call
ImageVideoGenerator.blackVideo(size:)with your target render dimensions (e.g., 320 × 180). - Build a manifest – Create a
MediaManifestand append aMediaManifestEntrypointing to the fixture’s absolute path. - Create a resolver – Instantiate
MediaResolverwith closures returning the manifest and an optional project URL. - Construct the timeline – Use helper functions from
Tests/PalmierProTests/Fixtures.swiftto createClipobjects and assemble aTimelinewith matching width/height. - Set the output destination – Generate a unique temporary URL in
NSTemporaryDirectory()with a.mp4extension. - Execute the export – Await
ExportService().export(...)with your chosenExportFormat(e.g.,.h264) andExportResolution(e.g.,.r720p). - Assert service state – Verify
svc.error == nil,svc.progress == 1.0, and that the output file exists. - Validate the asset – Load the exported file with
AVURLAssetand check thatduration.secondsmatches expectations and video tracks are present. - Clean up – Remove the temporary file in a
deferblock to prevent disk pollution.
Complete Code Example: Export Round-Trip Test
The reference implementation in ExportServiceRoundTripTests.swift demonstrates a full integration test using the Swift Testing framework (@Suite and @Test attributes):
// 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 validates that keyframes starting at clip offset zero do not crash the export:
@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:
// 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– Central orchestration of the AVFoundation export pipeline andAVAssetExportSessionmanagement.Sources/PalmierPro/Models/MediaManifest.swift– DefinesMediaManifestandMediaManifestEntryfor registering external assets.Sources/PalmierPro/Models/Timeline.swift– ContainsTimelineandClipstructs describing the video sequence and canvas dimensions.Sources/PalmierPro/Preview/ImageVideoGenerator.swift– ProvidesblackVideo(size:)for generating test fixtures.Tests/PalmierProTests/Fixtures.swift– Helper functions (Fixtures.clip,Fixtures.timeline) for rapid test construction.Tests/PalmierProTests/Export/ExportServiceRoundTripTests.swift– Reference implementation showing the full integration test pattern.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
@Suiteand@Testattributes with@MainActorfor UI-related tests - Always verify exported files with
AVURLAssetto confirm duration and track presence - Leverage
Fixtures.swifthelpers 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 example. This prevents accumulation of test artifacts in NSTemporaryDirectory() across test runs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →