Skip to content

Store with TestClock not awaiting when using SnapshotTesting #35

Description

@lucianolang

Description

There seems to be an issue with the Store when using a TestClock, the view doesn't react to timer ticks unless we assert for state mutation and recreate the view and the store.

Current versions:
swift-clocks 1.0.2
swift-composable-architecture 1.10.4
swift-snapshot-testing 1.16.0

Checklist

  • If possible, I've reproduced the issue using the main branch of this package.
  • This issue hasn't been addressed in an existing GitHub issue or discussion.

Expected behavior

These tests should pass

@MainActor // ❌
  func testExampleFailing() async {

    let clock = TestClock()
    let store = Store(initialState: .init(), reducer: { ContentViewFeature()}, withDependencies: { $0.continuousClock = clock })
    let sut = ContentView(store: store)

    await store.send(.startTimer).finish()

    await clock.advance(by: .seconds(1))
    // Test ends with unexpected result, timer doesn't advance
    assertSnapshot(matching: UIHostingController(rootView: sut), as: .image)

    // Same here
    await clock.advance(by: .seconds(4))
    assertSnapshot(matching: UIHostingController(rootView: sut), as: .image)
  }

  @MainActor // ❌
  func testExampleFailing2() async {

    let clock = TestClock()
    let store = TestStore(initialState: .init(), reducer: { ContentViewFeature()}, withDependencies: { $0.continuousClock = clock })
    store.exhaustivity = .off
    let sut = ContentView(store: store.snapshotStore)

    let task = await store.send(.startTimer)

    // Test ends with unexpected result, timer doesn't advance
    await clock.advance(by: .seconds(1))
    assertSnapshot(matching: UIHostingController(rootView: sut), as: .image)

    // Test ends with unexpected result, timer doesn't advance
    await clock.advance(by: .seconds(4))
    assertSnapshot(matching: UIHostingController(rootView: sut), as: .image)

    await task.cancel()
  }

Actual behavior

These tests are the only ones that I'm able to make work

  @MainActor // ✅
  func testExampleMutatingState() async {
    let clock = TestClock()
    let store = Store(initialState: .init(), reducer: { ContentViewFeature()}, withDependencies: { $0.continuousClock = clock })

    await store.send(.startTimer).finish()

    // State does get mutated
    await clock.advance(by: .seconds(1))
    XCTAssertEqual(store.secondsElapsed, 1)

    await clock.advance(by: .seconds(4))
    XCTAssertEqual(store.secondsElapsed, 5)
  }

  @MainActor // ✅
  func testExampleWithUISnapshot() async {
    let clock = TestClock()
    let store = TestStore(
      initialState: ContentViewFeature.State(),
      reducer: { ContentViewFeature() },
      withDependencies: { $0.continuousClock = clock }
    )
    let task = await store.send(.startTimer)

    let sut1 = ContentView(store: store.snapshotStore)
    assertSnapshot(matching: UIHostingController(rootView: sut1), as: .image)

    await clock.advance(by: .seconds(1))
    // By asserting the state mutation we mamage to await
    await store.receive(\.timerTicked) { $0.secondsElapsed = 1 }

    // If we recreate the view and the store with the current state then we get the desired result
    let sut2 = ContentView(store: store.snapshotStore)
    assertSnapshot(matching: UIHostingController(rootView: sut2), as: .image)

    // Same concept here
    await clock.advance(by: .seconds(4))
    for second in 2...5 {
      await store.receive(\.timerTicked) { $0.secondsElapsed = Double(second) }
    }
    let sut3 = ContentView(store: store.snapshotStore)
    assertSnapshot(matching: UIHostingController(rootView: sut3), as: .image)

    await task.cancel()
  }

Steps to reproduce

  1. Create a view with a timer
  2. A store that uses a ContinuousClock
  3. Create a test for a view that injects a TestClock
  4. Use assertSnapshot to assert changes in the view

Find here the project:
clockTests.zip

Thanks in advance

swift-clocks version information

1.0.2

Destination operating system

iOS 17.4

Xcode version information

15.3

Swift Compiler version information

swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
Target: arm64-apple-macosx14.0

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions