Search code examples
iosswiftkingfisherswift-snapshot-testing

Snapshot testing of views containing Kingfisher async images on iOS


I'm trying to create a snapshot test using swift-snapshot-testing of a UIView that contains an async image managed by Kingfisher. To avoid depending on internet connection my approach is using a local file url instead of an external url pointing to the test image.

My issue is that, although I'm using a local image, the test still ends before the image is retrieved.

I got same results doing a quick test with Nuke.

What should I do to manage this situation ?

Here is the test code:

@MainActor func testSnapshot() throws {

    let bundle = Bundle(for: Self.self)
    let imageURL = try XCTUnwrap(bundle.url(forResource: "myImage", withExtension: "png"))
    let state = WeatherInfoView.State(temperature: "18º",
                                      text: "Overcast clouds",
                                      imageURL: imageURL,
                                      location: "Bonjal, Indonesia")
    let view = WeatherInfoView()
    view.apply(state: state)

    view.widthAnchor.constraint(equalToConstant: 424).isActive = true
    view.heightAnchor.constraint(equalToConstant: 424).isActive = true

    assertSnapshot(of: view, as: .image)
}

And this is the view related code:

extension WeatherInfoView: StatefulView {

    struct State {
        let temperature: String
        let text: String?
        let imageURL: URL?
        let location: String
    }

    func apply(state: State) {
        temperatureLabel.text = state.temperature
        descriptionLabel.text = state.text
        imageView.kf.setImage(with: state.imageURL)
        locationLabel.text = state.location
    }
}

In the past I was using as a workaround solution passing a placeholder image and use it on the snapshot tests. But I'm not totally happy adding properties to the view state that are used just for the tests.


Solution

  • In your test you can add the image to the Kingfisher cache so that it'll immediately be available when the view tries to retrieve it.

    image is the UIImage you want to be shown in the snapshot url can be any url, it doesn't even have to be valid, it just needs to be the same as the one passed to the view.

    KingfisherManager.shared.cache.store(
        image,
        forKey: url.cacheKey,
        toDisk: false
    )
    

    Another thing it that this could be reading/writing to the cache of your app depending on how your test target is setup (ie, if your app is the test host). A way to avoid that is to make a new ImageCache that is only used in the tests. Kingfisher can also be set to only look in the image cache so the tests will never try to make a network request.

    let imageCache = ImageCache(name: "test-image-cache")
    imageCache.store(
        image,
        forKey: url.cacheKey,
        toDisk: false
    )
    
    KingfisherManager.shared.defaultOptions = [
        .onlyFromCache,
        .targetCache(imageCache)
    ]