AppIntentsTesting is a brand-new integration testing framework that lets you run App Intents directly on-device from an XCUITest bundle, verifying intents, entity queries, and entity chaining without mocks, stubs, or importing app code.
⢠Executes intents through the full App Intents stack ā the exact same code path Siri, Shortcuts, and Spotlight use ā catching regressions that unit tests would miss.
⢠Tests live in a standard XCUITest bundle so CI picks them up automatically, and they remain stable even when your UI changes.
⢠Supports test-driven development: write a failing test first, implement the entity query or intent, then confirm it passes ā all on-device.
Demonstrates writing an AppIntentsTesting test that creates a calendar via an App Intent, then verifies the returned entity has the expected title using dynamic member lookup.
import XCTest
import AppIntentsTesting
final class CreateCalendarIntentTests: XCTestCase {
private var intentDefs: IntentDefinitions!
private var entityDefs: EntityDefinitions!
override func setUp() async throws {
try await super.setUp()
// Initialize definitions using the app's bundle identifier.
// No import of the app module is needed.
intentDefs = try IntentDefinitions(bundleIdentifier: "com.example.CometCal")
entityDefs = try EntityDefinitions(bundleIdentifier: "com.example.CometCal")
// Seed known data so each test starts from a clean state.
let seedDef = intentDefs.intents["SeedSampleEventsIntent"]
let seedIntent = try seedDef.makeIntent()
try await seedIntent.run()
}
func testCreateCalendarAddsCalendarWithCorrectTitle() async throws {
// 1. Look up the intent definition by name (no app import required).
let createCalendarDef = intentDefs.intents["CreateCalendarIntent"]
// 2. Build a populated intent instance by passing parameter values.
// AppEnum raw string values are converted automatically.
let intent = try createCalendarDef.makeIntent(
parameters: [
"name": "Occupy Saturn",
"color": "red" // raw value of the CalendarColor AppEnum
]
)
// 3. Run the intent on-device through the full App Intents stack.
let result = try await intent.run()
// 4. Use dynamic member lookup to read properties off the returned entity.
let title: String = try result.value.title
XCTAssertEqual(title, "Occupy Saturn",
"The newly created calendar should carry the name supplied to the intent.")
}
func testEventStringQueryReturnsMatchingEvent() async throws {
// Look up the EventEntity definition.
let eventEntityDef = entityDefs.entities["EventEntity"]
// Execute the EntityStringQuery on-device.
let results = try await eventEntityDef.entities(matching: "Cosmic Ray Calibration")
XCTAssertEqual(results.count, 1, "Exactly one event should match the search string.")
let eventTitle: String = try results[0].title
XCTAssertEqual(eventTitle, "Cosmic Ray Calibration")
}
}
Test targets must NOT import the app module directly ā you reference everything by bundle identifier and string keys, so typos in intent/entity names will cause runtime failures rather than compile-time errors. Parameter names are not auto-completed; consult your intent definitions carefully. Custom parameter types require conformance to IntentValueConvertibleWrapper.
Requires the test runner and the app under test to share the same development team for code signing. Tests run on a physical or simulated device; no special hardware required.
More iOS 27 APIs land every week.
Get notified when new capabilities are published ā no noise, just signal.