# How to Write Unit Tests for the Palmier Pro Backend

> Learn to write unit tests for the Palmier Pro backend using Swift and Apple's Testing framework. Discover @Suite, @Test macros, and #expect assertions for robust testing.

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

---

**Unit tests in Palmier Pro are written in Swift using Apple's modern Testing framework, importing the backend module with `@testable import PalmierPro`, and organized into suites using the `@Suite` and `@Test` macros with `#expect` assertions.**

The Palmier Pro backend, housed in the `palmier-io/palmier-pro` repository, provides a pure Swift foundation for video editing logic under `Sources/PalmierPro`. Writing unit tests for this backend involves leveraging the **Testing** framework—Apple’s successor to XCTest—to validate immutable model structures and deterministic business logic without UI dependencies. This guide covers the exact file structure, import patterns, and assertion patterns used throughout the codebase.

## Testing Architecture and Framework Setup

### The Testing Framework

Palmier Pro adopts Apple’s **Testing** framework instead of the older XCTest. This DSL provides a declarative, Swift-native syntax where tests are functions marked with `@Test` and grouped into structs annotated with `@Suite`.

In [`Tests/PalmierProTests/Timeline/TimelineRangeSelectionTests.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Tests/PalmierProTests/Timeline/TimelineRangeSelectionTests.swift), the pattern looks like this:

```swift
import Testing
@testable import PalmierPro

@Suite("Timeline range selection")
struct TimelineRangeSelectionTests {
    @Test func normalizationReversesInvertedRanges() {
        let selection = TimelineRangeSelection(start: 100, end: 50)
        #expect(selection.normalized.start == 50)
        #expect(selection.normalized.end == 100)
    }
}

```

The `#expect` macro evaluates boolean expressions and records failures without crashing the test runner. Unlike XCTest, you do not inherit from `XCTestCase` or use `XCTAssert` functions.

### Test Target Configuration

The test target is defined in [`Package.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Package.swift) alongside the main library. The directory structure follows Swift Package Manager conventions:

- **Source code**: `Sources/PalmierPro/` (containing [`Models/Timeline.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Models/Timeline.swift), [`ViewModel/EditorViewModel.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ViewModel/EditorViewModel.swift), etc.)
- **Test code**: `Tests/PalmierProTests/` (organized into subdirectories like `Timeline/`, `Models/`, `ClipMath/`)

The `@testable` attribute in `import PalmierPro` grants access to `internal` methods and initializers, allowing you to test implementation details without exposing them as public API.

## Writing Your First Unit Test

Follow these steps to add a new unit test to the Palmier Pro backend:

1. **Create a file** under `Tests/PalmierProTests/<Category>/` (e.g., [`Tests/PalmierProTests/Models/ClipTests.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Tests/PalmierProTests/Models/ClipTests.swift)).
2. **Import the required modules** at the top of the file:
   ```swift
   import Testing
   @testable import PalmierPro
   ```

3. **Declare a test suite** using a struct with the `@Suite` attribute, providing a descriptive string name.
4. **Write individual test functions** using the `@Test` attribute, using `#expect` for assertions and `#require` for preconditions that must succeed.

A minimal test file structure looks like this:

```swift
import Testing
@testable import PalmierPro

@Suite("Clip manipulation")
struct ClipManipulationTests {
    @Test func durationClampingDropsOutOfBoundsKeyframes() {
        // Arrange
        var clip = Clip(
            mediaRef: "sample.mov",
            startFrame: 0,
            durationFrames: 100,
            trimStartFrame: 0,
            trimEndFrame: 0,
            speed: 1.0,
            volume: 1.0,
            fadeInFrames: 0,
            fadeOutFrames: 0,
            fadeInInterpolation: .linear,
            fadeOutInterpolation: .linear,
            opacity: 1.0,
            transform: Transform(),
            crop: Crop(),
            linkGroupId: nil,
            captionGroupId: nil,
            textContent: nil,
            textStyle: nil,
            opacityTrack: nil,
            positionTrack: nil,
            scaleTrack: nil,
            rotationTrack: nil,
            cropTrack: nil,
            volumeTrack: KeyframeTrack<Double>(keyframes: [
                .init(frame: 80, value: -6),
                .init(frame: 120, value: -3)
            ])
        )
        
        // Act
        clip.setDuration(60)
        
        // Assert
        #expect(clip.durationFrames == 60)
        #expect(clip.volumeTrack?.keyframes.count == 1)
        #expect(clip.volumeTrack?.keyframes.first?.frame == 80)
    }
}

```

## Testing Core Models and Business Logic

### Validating Model Serialization

Because core models like `Timeline`, `Clip`, and `Track` conform to `Codable`, tests should verify round-trip serialization. Encode a model to JSON, decode it back, and assert equality:

```swift
@Test func clipSerializationRoundTrip() throws {
    let original = Clip(mediaRef: "test.mov", startFrame: 0, durationFrames: 30)
    let encoded = try JSONEncoder().encode(original)
    let decoded = try JSONDecoder().decode(Clip.self, from: encoded)
    #expect(original == decoded)
}

```

### Boundary Validation

Test that methods correctly clamp values to valid ranges. For example, when calling `clip.setFade(.left, frames: -5)`, the implementation should store `fadeInFrames` as `0` rather than crashing or accepting invalid input.

### Keyframe Sampling

The `KeyframeTrack` type provides deterministic interpolation. Create tracks with known values and assert that `sample(at:)` returns the expected interpolated result:

```swift
@Test func linearKeyframeInterpolation() {
    let track = KeyframeTrack<Double>(keyframes: [
        .init(frame: 0, value: 0.0),
        .init(frame: 100, value: 1.0)
    ])
    #expect(track.sample(at: 50) == 0.5)
}

```

## Running Tests Locally and in CI

Execute the full test suite from the repository root using the Swift Package Manager:

```bash
swift test

```

Alternatively, open the project in Xcode and press **⌘U** (or select **Product → Test**). The **Testing** framework integrates with Xcode’s Test Navigator, displaying `@Suite` and `@Test` declarations hierarchically.

The CI pipeline configured in GitHub Actions runs `swift test` on every push, ensuring that pure logic in `Sources/PalmierPro` remains regression-free across macOS versions.

## Summary

- **Palmier Pro** uses Apple’s **Testing** framework (`import Testing`) rather than XCTest, utilizing `@Suite` and `@Test` for structure.
- Tests reside in `Tests/PalmierProTests/` and gain internal access via `@testable import PalmierPro`.
- Core models in `Sources/PalmierPro/Models/` (such as [`Timeline.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Timeline.swift)) are pure structs with `Codable` conformance, enabling deterministic unit tests.
- Use `#expect` for assertions and `#require` for test preconditions.
- Run tests with `swift test` or Xcode’s Test action; the same command executes in CI pipelines.

## Frequently Asked Questions

### Does Palmier Pro use XCTest or the new Testing framework?

Palmier Pro uses Apple’s modern **Testing** framework (introduced as a successor to XCTest). You will see `import Testing` rather than `import XCTest`, and tests use `@Test` functions instead of `XCTestCase` subclasses.

### How do I access internal methods and initializers in my unit tests?

Use `@testable import PalmierPro` at the top of your test file. This attribute exposes `internal` access-level members to the test target, allowing you to validate construction logic and helper methods without making them public.

### Where should I place new test files in the repository?

Create new Swift files under `Tests/PalmierProTests/`, organizing them into subdirectories that mirror the source structure (e.g., `Tests/PalmierProTests/Models/` for tests covering `Sources/PalmierPro/Models/`).

### Can I run Palmier Pro backend tests without opening Xcode?

Yes. Since the project is a Swift Package Manager project defined by [`Package.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Package.swift), you can run the entire test suite from the command line with `swift test`. This works on any macOS host with the Swift toolchain installed, making it ideal for CI/CD pipelines.