Why does Bundle.main fail in packages, extensions and tests, and what do you use instead?
Predict first
In a unit test, what is Bundle.main?
// A: the test bundle containing your tests
// B: the test runner or host app
// C: the package under test
- A
- B
- C
Mental model. Asking Bundle.main for a package’s file is checking the front door of the wrong building.
Bundle.main is the running process’s main bundle, not “the bundle my code is in”. In an app extension it is the extension. In a unit test it is the test runner or host app. In a Swift package, resources live in a separate resource bundle, so Bundle.main cannot see them.
Use the bundle that owns the resource. A Swift package target that declares resources gets a generated Bundle.module; pass it to loaders (Image("logo", bundle: .module), String(localized:bundle:)). In frameworks and tests use Bundle(for: SomeClass.self). In a package manifest, .process lets Xcode optimise resources (compile asset catalogues, localise strings), while .copy keeps files exactly as they are, folder structure included.
The main bundle belongs to the process, and the process is the test runner or host app. Use Bundle(for:) or Bundle.module for test resources.
Common traps
- Using
Bundle.mainin library code. It works in the demo app and silently fails for everyone else. - Using
.copyfor localised files. They are not processed, so localisation lookups miss them.
They might follow up with
- How does a test load a fixture file?
Bundle.modulein a package test target, orBundle(for: Self.self)in an Xcode test target. - What about static libraries? They have no bundle of their own; resources must come through a package resource bundle.
Related questions
- 2.6.1 What is a Bundle, and how do you load a resource from it?
- 2.6.3 How do you localise strings: Localizable.strings, string catalogs, String(localized:)?
← What is a Bundle, and how do you load a resource from it? How do you localise strings: Localizable.strings, string catalogs… →