Moya: How to return a stub from an Endpoint?

Created on 12 Aug 2017  路  18Comments  路  Source: Moya/Moya

I want to use a custom response when comming from UITests for a specific path.
So basically, when calling a path like "endPointThatShouldReturnAStub404" i want to return 404 and a sampleResponse. Otherwise i want to return regular stubs as you can see here (MoyaProvider<ApiProvider>(stubClosure: MoyaProvider.immediatelyStub)).
So when im passing an argument "CUSTOM_STUB" i want to stub reponses depending on the target.path. How can i do this?

    static func setProvider() -> MoyaProvider<ApiProvider> {
        var provider: MoyaProvider<TWApiProvider> = MoyaProvider<ApiProvider>(stubClosure: MoyaProvider.immediatelyStub)
        if ProcessInfo().arguments.contains("CUSTOM_STUB") {
            let endpointClosure = { (target: ApiProvider) -> Endpoint<ApiProvider> in
                switch target.path {
                case "endPointThatShouldReturnAStub404":
                    return Endpoint<ApiProvider>(url: url(route: target), sampleResponseClosure: {.networkResponse(404, target.sampleData)}, method: target.method, parameters: target.parameters)
                default:  ///endPointThatShouldReturnARegularStub
                    return Endpoint<ApiProvider>(url: url(route: target), sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters)
                }
            }
  //  **The problem is here cause im adding a default endpointClosure**
            provider = MoyaProvider<TWApiProvider>(endpointClosure: endpointClosure)
        }
        return provider
    }
question

Most helpful comment

@ivangodfather The stubClosure argument for the MoyaProvider initializer is a closure that takes in a TargetType and returns StubBehavior. You can pass it a custom closure that switches on the cases of the target and returns the respective stub behavior that you'd like. Is this what you were looking for?

Example:

let stubClosure = { (target: TargetType) -> Moya.StubBehavior in 
    switch target.path {
    case "endPointThatShouldReturnAStub404":
        return .immediate
    default:
        return .never
    }
}

All 18 comments

@ivangodfather The stubClosure argument for the MoyaProvider initializer is a closure that takes in a TargetType and returns StubBehavior. You can pass it a custom closure that switches on the cases of the target and returns the respective stub behavior that you'd like. Is this what you were looking for?

Example:

let stubClosure = { (target: TargetType) -> Moya.StubBehavior in 
    switch target.path {
    case "endPointThatShouldReturnAStub404":
        return .immediate
    default:
        return .never
    }
}

Not exactly.
I want also for the default case continue stubbing. And for "endPointThatShouldReturnAStub404" return a custom stub.

So imagine this situation, you have all your endpoints stubbed and no other requeriment.
You can have something like: var provider: MoyaProvider<TWApiProvider> = MoyaProvider<ApiProvider>(stubClosure: MoyaProvider.immediatelyStub)
This is ok since stubs all your requests with a 200 with a json from sampleData.
Now imagine you want to do a test for a specific endpoint that relies that other still stubbed as before but for this endPoint you want to return a 404 and another json.

So in other words:

let stubClosure = { (target: TargetType) -> Moya.StubBehavior in 
    switch target.path {
    case "endPointThatShouldReturnAStub404":
        return // i want to return a custom stub like 404 and specific json
    default:
        return .immediate
    }
}

@ivangodfather Sorry, I think I understand better now. The example was just that -- an example 馃槃.
What's wrong with your current approach?

@SD10 with my first approach?
returning return Endpoint<ApiProvider>(url: url(route: target), sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters)i get no stub, it just do the network call when i call apiProvider.request(.404case), i want to get the sampleClosure

@ivangodfather Are you calling setProvider() before every request?

@SD10 why you think with just this

                default:  ///endPointThatShouldReturnARegularStub
                    return Endpoint<ApiProvider>(url: url(route: target), sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters)

i would get a stub instead of a regular network call?
No, i just set this when creating the provider.
let apiProvider = UITestsSupport.setProvider()

@SD10 just thing you have an API Contract but the API still not ready. So you just create a provider with (stubClosure: MoyaProvider.immediatelyStub) and in your sample data you just return what the backend would return with 200 status code. But if you also want to test that what would happen when a certain endpoint fails you need more than that, i dont want to return the sampleData i defined there cause its the regular response, i want to customize that, makes more sense?

@ivangodfather I think there may be some issues with the initialization of the MoyaProvider.

// Here when you're passing the new endpointClosure you're not giving it stubBehavior
// it defaults to `.never`
provider = MoyaProvider<TWApiProvider>(endpointClosure: endpointClosure)

// I think it should be
provider = MoyaProvider<TWApiProvider>(endpointClosure: endpointClosure, stubClosure: MoyaProvider.immediatelyStub)

Sorry I wasn't able to help but I will check back later 馃槥 Let me know if you resolve this.

@SD10 i know, the think is that i don't want to never stub, i want to still stubbing, but with a customized stub for a specific path.

@ivangodfather Did you see the edit in my most recent comment? It looks like you're overwriting the stub behavior you provided in the first initialization of MoyaProvider.

With this approax:

provider = MoyaProvider<TWApiProvider>(endpointClosure: endpointClosure, stubClosure: MoyaProvider.immediatelyStub)

How in my tests i determine which calls are stubbed are which are not? Seems i'm missunderstood that cause makes no sot much sense to me that you actually can create a Provider setting both, but still unsure how to determine which ones goes into which closure.
And yes i know i'm overriding it, cause its not the the behaviour that i want when i found set "CUSTOM_STUB".

@ivangodfather Can you see if this documentation helps? Providers.md

Basically, your stubClosure handles the mapping of a (TargetType) -> StubBehavior.
This allows the MoyaProvider to have different stub behavior based on the target.

Similarly, the endpointClosure handles the mapping of a (TargetType) -> Endpoint.
This allows the MoyaProvider to have different endpoints based on the target.
You need this because you want to return a 404 response for some targets.

It looks like you will need both a custom stubClosure and endpointClosure to achieve the behavior you want. Am I making sense?

My final recommendation is to maybe do your tests in a Unit Test Target. Then create a mock provider solely for testing in the XCTestCase.

But in any case seem i didnt explain it properly what i wanted to achieve.

By parts:

1- One and most important
This initialization was till i opened the tick perfecly fine, and i want to continue the sampleData from my Provider still gets returned. Cause there is NO real API at this moment.

var provider: MoyaProvider<TWApiProvider> = MoyaProvider<ApiProvider>(stubClosure: MoyaProvider.immediatelyStub)

2- Now that i have all the network calls stubbed from my previous initialization, i want for an specific path to return a 404 response. Just in the case that "CUSTOM_STUB" is set. Maybe i need to create another Provider instead of doing this, i dunno.

3- Im doing this from UITests

Im gonna re-read the doc and tell you about if i see something clear this morning.

Doesnt help at all.
It says _But usually you want the same stubbing behavior for all your targets._ Yes for all the targets its ok, but i want for a specific one return a 404.

            let stubClosure = { (target: ApiProvider) -> Moya.StubBehavior in
                switch target {
                case .404case: ???
                default: return .immediate
                }
            }

In the 404 case i would like to return something like EndpointSampleResponse.networkResponse(404, Data()).
So from your previous comment i think i can't achieve with just a stubClosure. I need also a endPointClosure.

            let endPointClosure = { (target: ApiProvider) -> Endpoint<TWApiProvider> in
                switch target {
                case ..404case:
                    return ???
                default:
                    return //I dont want here to make a real request, i want to still stubbing.
                }
            }

In the 404case i want to return always the EndpointSampleResponse.networkResponse(404, Data()), not just creating an Endpoint like return Endpoint<ApiProvider>(url: url(route: target), sampleResponseClosure: {.networkResponse(404, target.sampleData)}, method: target.method, parameters: target.parameters) cause it will make the actual request since i dunno after creating a Regular endpoint how the calls can go throught the sample response if you dont create just a stubClosure when initializing. If you create a provider like this: let provider = MoyaProvider<MyService>()when the sampleResponse will be returning instead of the actual request? If you are in UnitsTests it gets returned instead of the request?

    static func setProvider() -> MoyaProvider<ApiProvider> {
        var provider: MoyaProvider<ApiProvider>!
        if ProcessInfo().arguments.contains("STUB_TAGS") {
            let endPointClosure = { (target: ApiProvider) -> Endpoint<ApiProvider> in
                let sampleClosure = { () -> EndpointSampleResponse in
                    switch target {
                    case .404target: return EndpointSampleResponse.networkResponse(400, target.sampleData)
                    default: return EndpointSampleResponse.networkResponse(200, target.sampleData)
                    }
                }
                return Endpoint<TWApiProvider>(url: url(route: target), sampleResponseClosure: sampleClosure)
            }

            provider = MoyaProvider<ApiProvider>(endpointClosure: endPointClosure)
        } else {
            provider = MoyaProvider<ApiProvider>(stubClosure: MoyaProvider.immediatelyStub)
        }
        return provider
    }

After reading again the docs I think this soltuion could work for me. But i dont know why if i initialize the Provider with this Endpoint the sample response closure never gets executed, instead it makes the real url calls.

@ivangodfather I'm trying to tell you that you're not receiving the sample response closure because your MoyaProvider is defaulting to a stubBehavior of .never.

// This line of code will not stub requests. It defaults to `.never`.
// This is why you are receiving an actual request and not stubbing.
provider = MoyaProvider<ApiProvider>(endpointClosure: endPointClosure)

// You need to provide a stub behavior to receive the sample response
provider = MoyaProvider<ApiProvider>(endpointClosure: endPointClosure, stubClosure: MoyaProvider.immediatelyStub)

Am I making sense?

I think i got it. So basically when you provide an endpointClosure and a stubClosure if you return .immediate or .delayed it will go to the endpointClosure and grab the sampleData.
So finally it works! Thx you a lot! If you are curious how i implemented this here it is:

    static func setProvider() -> MoyaProvider<ApiProvider> {
        var provider: MoyaProvider<ApiProvider>!
        if ProcessInfo().arguments.contains("STUB_TAGS") {
            let endPointClosure = { (target: ApiProvider) -> Endpoint<ApiProvider> in
                let sampleClosure = { () -> EndpointSampleResponse in
                    switch target {
                    case .400target:
                        return EndpointSampleResponse.networkResponse(400, target.sampleData)
                    default:
                        return EndpointSampleResponse.networkResponse(200, target.sampleData)
                    }
                }
                return Endpoint<ApiProvider>(url: url(route: target), sampleResponseClosure: sampleClosure)
            }
            provider = MoyaProvider<ApiProvider>(endpointClosure: endPointClosure, stubClosure: MoyaProvider.immediatelyStub)
        } else {
            provider = MoyaProvider<ApiProvider>(stubClosure: MoyaProvider.immediatelyStub)
        }
        return provider
    }

Nice to hear you've resolved the issue, and thanks for providing your implementation! 馃憤

Was this page helpful?
0 / 5 - 0 ratings