Skip to content

Extract WikiMessageCore framework so unit tests can run in CI #5

Description

@timfallmk

Background

WikiMessageExtensionTests exists with coverage for MessageBuilder, WikipediaService, HTTPClient, the DTOs, and Article — but the suite cannot currently run in CI. The PR #3 modernization branch was forced to drop test execution and run xcodebuild build only.

Why tests can't run today

iMessage extensions don't have a real launchable iOS host app. The WikiMessage target's product is Apple's MessagesApplicationStub (/Applications/Xcode_15.4.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Library/Application Support/MessagesApplicationStub), a placeholder binary whose only job is to embed the .appex. iOS's FrontBoard refuses to launch it, with:

Application "com.timfall.WikiMessage" is unknown to FrontBoard.

This means:

  • TEST_HOST = .../WikiMessage.app/WikiMessage → can't launch (FrontBoard rejects the stub)
  • TEST_HOST = .../WikiMessage MessagesExtension.appex/... → can't launch (.appex isn't a launchable bundle)
  • No TEST_HOST + BUNDLE_LOADER pointing at the .appex → linker resolves symbols but XCTest still has no host process to load the test bundle into

So the test bundle compiles but has nowhere to run.

Proposed fix: extract a shared framework

Create a WikiMessageCore framework (or Swift package) target containing the code that's worth testing — the parts that have no UI dependencies:

  • Data/Article.swift
  • Data/WikipediaService.swift
  • Data/DTOs/SummaryDTO.swift
  • Data/DTOs/SearchResponseDTO.swift
  • Messaging/MessageBuilder.swift (and probably MessageComposer.swift)
  • Networking/HTTPClient.swift
  • Networking/NetworkMonitor.swift

Both the extension and the test target link against this framework. The test target becomes a "logic test" — no TEST_HOST required, no host app launch, runs directly under xctest.

The SwiftUI views, MessagesViewController, AppModel, SearchModel, etc. stay in the extension target since they depend on UIKit/SwiftUI/Messages and aren't unit-testable in isolation anyway.

Steps

  1. Add a new framework target WikiMessageCore to the Xcode project (iOS 16, Swift 5.7, same concurrency settings as the extension).
  2. Move the files listed above from WikiMessage MessagesExtension/ into the framework target.
  3. Make types that need to be visible across the boundary public (most are already internal, so this is mostly DTO fields, Article's init, WikipediaService's methods, MessageBuilder.build, HTTPClient's public surface).
  4. Update the extension target to depend on and embed WikiMessageCore.
  5. Update the test target to:
    • Depend on WikiMessageCore
    • Drop BUNDLE_LOADER and TEST_HOST from build settings
    • Remove the WikiMessage target dependency
    • Switch @testable import WikiMessage_MessagesExtension → @testable import WikiMessageCore
  6. Re-enable the test step in .github/workflows/ci.yml (revert the change in commit fd8e889):
    clean test

Verification

  • xcodebuild test -scheme "WikiMessage MessagesExtension" -destination 'platform=iOS Simulator,name=iPhone 15,OS=latest' runs all tests successfully on macOS 14 + Xcode 15.4.
  • All 4 test files (SummaryDTOTests, SearchResponseDTOTests, WikipediaServiceTests, MessageBuilderTests) execute and pass.
  • Extension still builds and the iMessage app behaves identically — the framework split is an internal refactor with no user-visible change.

Why not just add the source files to the test target?

Compiling each file into both the extension and the test target works but produces duplicate symbols, breaks @testable import, and doubles incremental compile times. A framework is the architecturally correct boundary and what Apple recommends for testing extension code.

Tracked from

PR #3 (modernization branch).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions