Dependency testValue
Not Overridden
#1652
Replies: 12 comments 7 replies
-
Hey @TicTocCroc. I've just tried and your test is passing on my machine (albeit I had to annotate the |
Beta Was this translation helpful? Give feedback.
-
I linked an Xcode project in the "Steps to reproduce" section that recreates the problem for me. |
Beta Was this translation helpful? Give feedback.
-
Nevermind. I see the project I linked is incomplete for some reason. Here's another that should reproduce the issue. |
Beta Was this translation helpful? Give feedback.
-
Ah sorry, I didn't see. Indeed, a few files were missing, but I was able to reproduce the issue with your project by filing the gaps. struct ValueReducer: ReducerProtocol {
struct State: Equatable { var value: Int = 0 }
enum Action: Equatable {
case onAppear
case fetchValue
case valueResponse(TaskResult<Int>)
}
@Dependency(\.valueClient) var valueClient
func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
switch action {
case .onAppear:
return .task { return .fetchValue }
case .fetchValue:
return .task {
await .valueResponse(TaskResult { try await valueClient.fetch() } )
}
case let .valueResponse(.success(value)):
state.value = value
return .none
case .valueResponse(.failure): return .none
}
}
}
import XCTestDynamicOverlay
struct ValueClient {
var fetch: @Sendable () async throws -> Int
}
extension ValueClient {
static let live = Self(fetch: { return 0 })
static let preview = Self(fetch: { return 0 })
static let test = Self(fetch: unimplemented(placeholder: 0))
}
private enum ValueClientKey: DependencyKey {
static let liveValue = ValueClient.live
static let previewValue = ValueClient.preview
static let testValue = ValueClient.test
}
extension DependencyValues {
var valueClient: ValueClient {
get { self[ValueClientKey.self] }
set { self[ValueClientKey.self] = newValue }
}
}
@MainActor
final class ValueTests: XCTestCase {
func testValueOnAppear() async {
let store = TestStore(initialState: ValueReducer.State(), reducer: ValueReducer())
store.dependencies.valueClient = ValueClient(fetch: { 1 })
await store.send(.onAppear)
await store.receive(.fetchValue)
await store.receive(.valueResponse(.success(1))) {
$0.value = 1
}
}
} |
Beta Was this translation helpful? Give feedback.
-
@tgrapperon Are you saying that by putting everything in a single file it works, or is there some other change you made? |
Beta Was this translation helpful? Give feedback.
-
This one is more subtle than #1640 in any case: If we copy/paste the reducer/dependencies in the test target, and if remove the |
Beta Was this translation helpful? Give feedback.
-
@mbrandonw More precisely:
(by "Everything", I mean the "Reducer+Dependency" declarations) |
Beta Was this translation helpful? Give feedback.
-
Oh, I think this is simpler than #1640. I think it's just the app running in the simulator while the test is running. 🙄 If you comment out the view in the |
Beta Was this translation helpful? Give feedback.
-
I see. I originally tried to set up the test target to run without the host application, as I saw in one of the TCA episodes, but I couldn't get it working. Then I saw that the sample applications in TCA still run the hosting application, so I thought maybe that's no longer possible. Is it still possible to run the test target without the hosting application? |
Beta Was this translation helpful? Give feedback.
-
I'm going to convert this to an issue because it's not a bug per-se with the library, but rather a quirk of how testing works in iOS. If you didn't know, when you run tests for iOS, Xcode launches a simulator with your app. This means the entry point of your application will start executing even if it has nothing to do with the tests you are running. One work around would be doing something like this in the entry point: @main
struct ValueApp: App {
var body: some Scene {
WindowGroup {
ValueView(
store: Store(
initialState: ValueReducer.State(),
reducer: ValueReducer()
.dependency(\.context, .live)
)
)
}
}
} Or you could just omit your entry point from the application when running in tests by using XCTestDynamicOverlay: import XCTestDynamicOverlay
@main
struct ValueApp: App {
var body: some Scene {
WindowGroup {
if !_XCTIsTesting {
ValueView(
store: Store(
initialState: ValueReducer.State(),
reducer: ValueReducer()
)
)
}
}
}
} Would be curious to hear if others have more ideas. |
Beta Was this translation helpful? Give feedback.
-
While I agree with the term |
Beta Was this translation helpful? Give feedback.
-
@gcohen-dev The next release of TCA will fix this problem. See #1686. |
Beta Was this translation helpful? Give feedback.
-
Description
Problem
Dependencies do not get properly overridden when testing a specific sequence of actions and effects.
Below I have a simple reducer that has an
onAppear
action that triggers afetchValue
action. This action in turn produces an effect that uses aValueClient
dependency to fetch a value, whose result is delivered with avalueResponse
action.However, when I run the test below, making sure to override the dependency as shown in the documentation, the test always fails with the failure
testValueOnAppear(): Unimplemented
.It seems that the dependency override does not persist during the effect returned by the
fetchValue
action. Maybe I'm not setting up my dependency correctly, or not using effects correctly?Reducer
Dependency
Failing Test
Checklist
main
branch of this package.Expected behavior
I expect the overridden
ValueClient
dependency to be used during the effect while running the test, causing the test to pass.Actual behavior
The unimplemented version of the dependency is used instead, and the test fails.
Steps to reproduce
Running the single unit test in this Xcode project should recreate the issue.
The Composable Architecture version information
0.45.0
Destination operating system
iOS 16.1
Xcode version information
14.1 (14B47b)
Swift Compiler version information
Beta Was this translation helpful? Give feedback.
All reactions