vadymmarkov / Malibu

:surfer: Malibu is a networking library built on promises

Home Page:https://vadymmarkov.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

MockProvider does not call promise's callback

daneov opened this issue · comments

The following snippet shows a reproducible case in which neither success nor failure on the promise's part is called.

This seems to be in part due to the Networking instance being deallocated (the weak self check) returns since it is nil.

How can I work around this?

final class MockProviderSpec: QuickSpec {
    override func spec() {
        describe("Test") {
            it("Passes") {
                let fetcher = APIFetcher()
                waitUntil { done in
                    fetcher.request(request: CustomEndpoint.test).then({ _ in
                        done()
                    }).fail({ error in
                        done()
                    })
                }
            }
        }
    }
}

class APIFetcher {
    var mockClosure: ((RequestConvertible) -> Mock?)?

    func defaultClosure(_ request: RequestConvertible) -> Mock?{
        return Mock(json: [:])
    }

    func request(request: CustomEndpoint) -> NetworkPromise {

        let networking = Networking<CustomEndpoint>(mode: .sync, 
                                                    mockProvider: MockProvider<CustomEndpoint>(defaultClosure)
        )
        return networking.request(request)
    }
}

enum CustomEndpoint: RequestConvertible {
    static var baseUrl: URLStringConvertible?

    static var headers: [String : String] = [:]

    var request: Request {
        return Request.post("mock://derp")
    }

    case test
}

You could make networking an instance var in your test and then inject it to APIFetcher for example.

Sorry, due to oversimplifying the real case got lost :)

Here is a gist containing the actual case.

The execute<T>(request: T) -> NetworkPromise is required by the protocol, and relied upon by my services.

If I want to test a service, I thus mock the APIClient in them, and the Networking instance is configured in the APIMockClient, since in the real world, it is also responsible for configuring the Networking's headers and authentication token.

Now, in a real world example this approach works:

    func execute<T>(request: T,
                    mockProvider: MockProvider<T>? = nil) -> NetworkPromise where T: RequestConvertible {
        let networking = Networking<T>(mode: .async, mockProvider: mockProvider)
        networking.configureHeaders()
        networking.configure(token: userManager.accessToken)

        return networking.request(request).validate()

So I am a bit confused here, as the exact same approach is used.

When I use Malibu I usually keep a reference to Networking class somewhere. In your case it's not easy to find a workaround without changing the code. It would be easier if you had a generic APIMockClient with networking being an instance var, but it doesn't follow func execute<T>(request: T) method from APIClientMethods protocol.

I ended up making sure the Networking instance doesn't deallocate in the tests by retaining the object as follows:

func execute<T>(request: T,
                    mockProvider: MockProvider<T>? = nil) -> NetworkPromise where T: RequestConvertible {
        let networking = Networking<T>(mode: .async, mockProvider: mockProvider)
        return networking.request(request).validate().always({ _ in _ = networking })

Thanks for the help!