Moya: How to create a stubbing provider that returns stubs based on internal state

Created on 13 Jul 2016  ยท  6Comments  ยท  Source: Moya/Moya

So I have a view model that fetches articles from the internet and returns the changes in an array of (index, ChangeType) tuples. I basically want to test if a JSON response with 5 articles will emit an array of 5 inserts and then the next request should return a partially modified JSON that should make the viewModel emit one insertion and one update.

To test this I thought I could just make a custom provider that returns different stubs based on internal state ("isFirstResponse = true") of the provider but since the endpointClosure is a constant I can only do it in the init. This doesn't work though, because I would need to put self in the capture list of the closure, which is not yet initialised at this point.

The closure only gets the Target as an input, which doesn't change (I still request the same route for articles), so how would you model this?

Any help appreciated and thank you for his amazing framework!

question

Most helpful comment

This is a great question I looked through my own code, because this is something I've had to test to. The code I found is a little old and doesn't apply directly to this question.

So the way I would approach this fresh is that you don't necessarily need the state to be internal to the testing provider. When you create a provider in your tests, you can pass in an endpointClosure that accesses an isFirstResponse variable defined outside the closure, within the test. Set it to true before the test begins and the first time the enpointClosure is called, set it to false. Not sure what testing framework you're using โ€“ if you provide the code that you've tried so far, I can give you an idea of how I would approach it.

Moya's architecture places a large emphasis on testing, but most of our documentation discusses only how to use Moya in production code. I think it's worth adding documentation demonstrating best practices and common techniques when using Moya with testing. @fruitcoder if you have any ideas on what could be included, let us know ๐Ÿ‘

All 6 comments

This is a great question I looked through my own code, because this is something I've had to test to. The code I found is a little old and doesn't apply directly to this question.

So the way I would approach this fresh is that you don't necessarily need the state to be internal to the testing provider. When you create a provider in your tests, you can pass in an endpointClosure that accesses an isFirstResponse variable defined outside the closure, within the test. Set it to true before the test begins and the first time the enpointClosure is called, set it to false. Not sure what testing framework you're using โ€“ if you provide the code that you've tried so far, I can give you an idea of how I would approach it.

Moya's architecture places a large emphasis on testing, but most of our documentation discusses only how to use Moya in production code. I think it's worth adding documentation demonstrating best practices and common techniques when using Moya with testing. @fruitcoder if you have any ideas on what could be included, let us know ๐Ÿ‘

Thanks @ashfurrow for the great reply, I just took a look at your example (I've already taken a look at the whole project for reference). So you take a look at the page - a parameter depending on the target - which is a perfect fit here.

I already implemented my test before I read your answer. I use a global variable that is changed after the first request was made and the test works fine. Personally, I would have preferred to keep the state inside the Provider subclass as a static variable, but Static store properties not yet supported in generic types, so I am happy for now.

It's a great idea to discuss general best practices for Moya in the documentation (especially for testing), since I find myself so often trying to apply the sometimes specific solutions in the Artsy project to my own. I can think of special test providers, that I already use in my tests (like FailingProvider, which always returns a 404 response similar to a behaviour described in #533).

Also, I still haven't found a best practice on how to handle my stubs. I currently have them as JSON files in the test target, which works ok, but I would prefer to have a setup like:

{ define values as variables }
{ create JSON with variables }
{ give JSON to provider as a stubbed response }
{ use provider to get response }
{ - parsing: check that the values returned are correctly parsed by comparing them with the variables declared above }
{ - viewModel: check that the viewModel returns correct values based on variables declared above
}

The motivation behind this is that I can't make a typo when asserting something by reusing the variables (actually constants) instead of taking a look at the value in my JSON stub. I've seen that you already discussed on how to inject the responses easier but I just can't find out where it was. It looked very promising!

Sounds like you might be talking about this issue: https://github.com/Moya/Moya/issues/56 That's actually our _oldest_ open issue, something we've wanted for a while ๐Ÿ˜„

So I think testing examples/demos should include things like the following:

  • How to create a provider that always stubs (a "Hello, World" for testing with Moya).
  • How to verify that a specific request was actually performed.
  • How to verify that two different requests were actually made.
  • How to return a different status code than 200.
  • How to return different response data depending on parameters of the request.
  • How to handle errors returned from a requestClosure (like an OAuth error).
  • How to return different response data depending on how many times it's already been invoked (an example of polling for a specific response).

It could be really cool to include both a short code snippet showing the technique and also a link to an open source project that actually uses that technique. I can find Artsy examples once we have a list of everything we want to demonstrate. @fruitcoder any techniques you think we should add to this list?

That's EXACTLY the issue I was talking about!

The list looks like a very good start and linking to an actual implementation sounds great as well ๐Ÿ‘
Having just reread the list, I really cannot think of anything else right now. It would cover all my testing scenarios.

Another thing that doesn't relate to testing specifically is how to handle pagination. I don't know if there should be a snippet for that as well (especially if you want to focus on testing practices first). Currently, I use the following approach:

public struct PaginationInfo {
  let page: Int
  let limit: Int
  let offset: String?
}

enum MyAPI {
  case Search(query: String, pagination: PaginationInfo?)
  case UserByName(name: String, pagination: PaginationInfo?)
  ...

  func appending(paginationInfo info: PaginationInfo) -> MyAPI {
    switch self {
      case let Search(query, _):
        return Search(query: query, pagination: info)
      ...   
    }
  }
}

Pagination might work differently for each API, but a general approach might be helpful. The client then uses the appending function inside the request call to add the pagination info.

Anyways, do you want to open a new issue for the community to discuss the list?

Cool ๐Ÿ‘ New issue sounds like a good idea, link back to this one, maybe copy over the list too. I think the new docs around testing should go in a new markdown file called Testing.md, if you're keen to to help out a pull request would be welcome!

I've opened https://github.com/Moya/Moya/issues/632 to address improves to the documentation, going to close this, feel free to re-open.

Was this page helpful?
0 / 5 - 0 ratings