@mbrandonw pointed out the solution to an almost identical question in this thread: It's possible to use dependencies in eagerly evaluated closures.
I followed the solution in the other thread, put the closure at the top level and overwrote it from the initializer with a test value:
public let id: UUID = {
@Dependency(\.uuid) var uuid: UUIDGenerator
return uuid()
}()
public init(id: UUID? = nil, _ content: Content, folder: URL? = nil) {
if let id {
self.id = id
}
self.content = content
}
The problem with this is, that it runs on every initialization, producing "dependency not implemented" errors when creating the expected test result state.
(Identifiable
forbids making it a lazy
variable, a function cannot be executed from init
and having it return a UUIDGenerator
instead of a UUID
makes State
no longer conform to Equatable
.)
What worked for me was putting the closure into the initializer:
public init(
id: UUID = {
@Dependency(\.uuid) var uuid: UUIDGenerator
return uuid()
}(),
_ content: Content,
) {
self.id = id
self.content = content
}
Or in a static helper method:
public init(
id: UUID = getUUID(),
_ content: Content
) {
self.id = id
self.content = content
}
public static func getUUID() -> UUID {
@Dependency(\.uuid) var uuid: UUIDGenerator
return uuid()
}
This lets me test the parent feature.
Problem is with the parent of the parent where I don’t have direct access to the initializer anymore. So when I create an expected test state of the grandparent I would need to pipe the expected UUID of the grandchild through the object in the middle.
If I don’t do that my test doesn’t only fail because of the UUID mismatch, but also because of the not implemented UUID dependency (creating the expected state happens outside the store).
So my parent initializer looks like this (irrelevant parts omitted):
public init(
paragraphUUIDs: [UUID]? = nil
) {
let paragraphs = ((try? Content.parser.parse(entry.content)) ?? [])
let finalParagraphs: [ContentFeature.State]
if let paragraphUUIDs { // used in test
finalParagraphs = zip(paragraphUUIDs, paragraphs)
.map { (uuid, content) in
ContentFeature.State(
id: uuid,
content
)
}
} else { // used in production
finalParagraphs = paragraphs
.map { content in
ContentFeature.State(
content
)
}
}
self.paragraphs = IdentifiedArrayOf(uniqueElements: finalParagraphs)
}
This passes the tests but it looks very hacky.