Apple pay unit testing - ios

I'm working with apple pay on iOS 11
I have unit testing for our credit card tokenization. But I'm trying to tokenize an apple pay request.
Is it possible to unit test apple pay?
import XCTest
import PassKit
#testable import os_ios
extension apiTests{
func testBraintreeApplePay(){
let expect = expectation(description: "testBraintreeApplePay")
var responseReceived = false
let number = "4111111111111111"
let month = "12"
let year = "20"
let securityCode = "411"
let postalCode = "70433"
let payment = PKPayment()
// add the above info to this payment
Braintree.paymentAuthorizationViewController(
didAuthorizePayment: payment,
completion: {(status: PKPaymentAuthorizationStatus,nonce:String?) in
responseReceived = true
// Check to see if we've received a nonce
expect.fulfill()
})
waitForExpectations(timeout: expectationTimeoutDuration, handler: nil)
XCTAssert(responseReceived)
}
}

In order to initialize 'PKPaymentAuthorizationController', you need to inject the value that each specific test needs. In the case of Apple Pay, it usually involves the method 'canMakePayments(usingNetworks: [PKPaymentNetwork]) -> Bool' and the failable initializer for the 'PKPaymentAuthorizationController' or 'PKPaymentAuthorizationViewController'.
You can do it either by constructor injection (passing the values on the initializer) or by property injection.
Here's an example of a class that handles authorization controller initialization, and completes with the controller, or completes with an error.
public class PaymentAuthorizationHandler {
public typealias ApplePayControllerFactory = (PKPaymentRequest) -> PKPaymentAuthorizationViewController?
public typealias ApplePayCompletionHandler = (Result<PKPaymentAuthorizationViewController, Error>) -> Void
public typealias NetworkAuthorizationHandler = ([PKPaymentNetwork]) -> Bool
public enum Error: Swift.Error {
case networkNotSupported
case unableToInitialize
}
private let controllerFactory: ApplePayControllerFactory
private let networkAuthorizationHandler: NetworkAuthorizationHandler
public init(
controllerFactory: #escaping ApplePayControllerFactory = PKPaymentAuthorizationViewController.init,
networkAuthorizationHandler: #escaping NetworkAuthorizationHandler = PKPaymentAuthorizationViewController.canMakePayments
) {
self.controllerFactory = controllerFactory
self.networkAuthorizationHandler = networkAuthorizationHandler
}
private func makeRequest() -> PKPaymentRequest { //... }
public func requestAuthorization(completion: ApplePayCompletionHandler) {
let request = makeRequest()
guard networkAuthorizationHandler(request.supportedNetworks) else {
completion(.failure(.networkNotSupported))
return
}
guard let paymentVC = controllerFactory(request) else {
completion(.failure(.unableToInitialize))
return
}
completion(.success(paymentVC))
}
}
The view controller initializer and the 'canMakePayments(usingNetworks: [PKPaymentNetwork]) -> Bool' method signature are being injected via constructor injection, represented by the typealiases. With this change, it’s possible to pass, in production, the real method, and for test use, the value required for the test case can be provided by injecting it during the test setup.
Here's an example of the tests:
func test_requestAuthorization_failsWhenControllerInitializationFails() {
let authController = PKPaymentAuthorizationViewController(paymentRequest: .invalidRequest)
let sut = PaymentAuthorizationHandler(controllerFactory: { request in
authController
}, networkAuthorizationHandler: { networks in
true
})
let exp = expectation(description: "Wait for request permission")
var expectedError: PaymentAuthorizationHandler.Error?
sut.requestAuthorization { response in
switch response {
case let.success(controller):
XCTFail("Expected failure, received \(String(describing: controller)) instead")
case let .failure(receivedError):
expectedError = receivedError
}
exp.fulfill()
}
wait(for: [exp], timeout: 1.0)
XCTAssertEqual(expectedError, .unableToInitialize)
}
func test_requestAuthorization_succeedsWhenPaymentNetworkSupported() {
let authController = PKPaymentAuthorizationViewController(paymentRequest: .validRequest)
let sut = PaymentAuthorizationHandler(controllerFactory: { request in
authController
}, networkAuthorizationHandler: { networks in
true
})
let exp = expectation(description: "Wait for request permission")
sut.requestAuthorization { response in
switch response {
case let.success(receivedController):
XCTAssertNotNil(receivedController, "Request might be incomplete, or payment is not possible")
case let .failure(receivedError):
XCTFail("Expected success, received \(receivedError) instead")
}
exp.fulfill()
}
wait(for: [exp], timeout: 1.0)
}
You can check a more thorough explanation here.

Related

Success case is not being called from a viewModel test

I am new to working with dependency injection. When I run a test with a mocked object the success result case isn't getting triggered and the object isn't added to my #Published property which I am trying to test.
When I go to test my viewModel I can see the array to be tested but when getShows() gets called in the test the Result case is used.
Test File
import XCTest
#testable import PopularTVViewer
class PopularTVViewerTests: XCTestCase {
func testPopularTVModel() {
let mockedManager = MockedPopularTVManager()
mockedManager.result = .success(mockedManager.mockPopularShows)
let viewModel = PopularTVViewModel(manager: mockedManager)
#warning("getShows() success case isn't being called even through viewModel has its reference.")
viewModel.getShows()
XCTAssertNotNil(viewModel)
XCTAssertNotNil(mockedManager.result)
// Currently failing
// XCTAssertEqual(viewModel.popularTV.count, 4)
XCTAssertEqual(mockedManager.getPopularShowsCallCounter, 1)
}
}
MockedManager
class MockedPopularTVManager: PopularTVManagerProtocol {
var result: Result<[PopularTV], NetworkError>!
var getPopularShowsCallCounter = 0
func getPopularShows(completion: #escaping (Result<[PopularTV], NetworkError>) -> Void) {
completion(result)
getPopularShowsCallCounter += 1
}
let mockPopularShow = PopularTV(name: "House of the Dragon", posterPath: "/mYLOqiStMxDK3fYZFirgrMt8z5d.jpg", popularity: 4987.163, voteAverage: 7.7, voteCount: 881)
let mockPopularShows = [
PopularTV(name: "The Lord of the Rings: The Rings of Power", posterPath: "/mYLOqiStMxDK3fYZFirgrMt8z5d.jpg", popularity: 4987.163, voteAverage: 7.7, voteCount: 881),
PopularTV(name: "House of the Dragon", posterPath: "/z2yahl2uefxDCl0nogcRBstwruJ.jpg", popularity: 4979.127, voteAverage: 8.6, voteCount: 1513),
PopularTV(name: "She-Hulk: Attorney at Law", posterPath: "/hJfI6AGrmr4uSHRccfJuSsapvOb.jpg", popularity: 2823.739, voteAverage: 7.1, voteCount: 846),
PopularTV(name: "Dahmer – Monster: The Jeffrey Dahmer Story", posterPath: "/f2PVrphK0u81ES256lw3oAZuF3x.jpg", popularity: 1774.56, voteAverage: 8.3, voteCount: 402)
]
}
ViewModel
final class PopularTVViewModel: ObservableObject {
#Published var popularTV = [PopularTV]()
let manager: PopularTVManagerProtocol
let columns = ColumnLayoutHelper.threeColumnLayout
// Injecting for testing.
init(manager: PopularTVManagerProtocol = PopularTVManager()) {
self.manager = manager
}
// Grabbing next page of results
func getMoreShows() {
getShows()
}
// Initial network call.
func getShows() {
manager.getPopularShows() { [weak self] result in
DispatchQueue.main.async {
switch result {
case .success(let popularTV):
for show in popularTV {
self?.popularTV.append(show)
}
case .failure(let error):
switch error {
case .invalidURL:
print("Invalid URL")
case .invalidData:
print("Invalid Data")
case .unableToComplete:
print("Unable to complete")
case .invalidResponse:
print("Invalid response")
}
}
}
}
}
}
I've done everything I can think of to make sure the object exists and the viewModel has access to it but its as if the mockedManager in my test doesn't have the success when getShows() is run.
The test is failing because you are switching threads during your test. The DispatchQueue.main.async in your PopularTVViewModel is causing it to fail.
You need to remove the use of DispatchQueue.main.async from your test. This is possible to do.
We first need to create a function that will stand in place of the call to DispatchQueue.main.async. This function will check that we are on the main thread and if we are execute the code directly without switching threads, otherwise if we are on a background thread it will dispatch on the main thread. This should mean that in your application it works exactly the same as before, and in your tests it avoids the thread hop so that they now pass.
/// You could make this a global function, an extension on DispatchQueue,
/// the choice where to put it is up to you, but it should be accessible
/// by whichever classes need to use it as chances are you may need to use
/// it in multiple places.
func performUIUpdate(using closure: #escaping () -> Void) {
if Thread.isMainThread {
closure()
} else {
DispatchQueue.main.async(execute: closure)
}
}
We can then update your PopularTVViewModel to use the new function.
final class PopularTVViewModel: ObservableObject {
#Published var popularTV = [PopularTV]()
let manager: PopularTVManagerProtocol
let columns = ColumnLayoutHelper.threeColumnLayout
// Injecting for testing.
init(manager: PopularTVManagerProtocol = PopularTVManager()) {
self.manager = manager
}
// Grabbing next page of results
func getMoreShows() {
getShows()
}
// Initial network call.
func getShows() {
manager.getPopularShows() { [weak self] result in
performUIUpdate { // Note we use the new function here instead of DispatchQueue.main.async
switch result {
case .success(let popularTV):
// you could use the following instead of your for loop.
// self?.popularTV.append(contentsOf: popularTV)
for show in popularTV {
self?.popularTV.append(show)
}
case .failure(let error):
switch error {
case .invalidURL:
print("Invalid URL")
case .invalidData:
print("Invalid Data")
case .unableToComplete:
print("Unable to complete")
case .invalidResponse:
print("Invalid response")
}
}
}
}
}
}
Your tests should now pass.
There is a great article by John Sundell that shows how to reduce flakiness in testing.
Also this book, iOS Unit Testing by Example, by Jon Reid is very good and well worth having on your bookshelf.

Log Apollo iOS Client GraphQL query variables and url to console?

I would like to print the url of my Apollo iOS Client GraphQL queries to the Xcode console when the query is called.
You don't need to write swift code to extract the QueryName.
Use Proxyman, like Charles Proxy. It will display the QueryName on the column by default.
Ref: https://docs.proxyman.io/advanced-features/graphql
Per the Apollo iOS Client docs, a logging interceptor can be added in a custom Interceptor Provider.
I created a custom interceptor provider using the code from DefaultInterceptorProvider, and included the logging interceptor.
import Apollo
class InterceptorProviderWithLogging: InterceptorProvider {
private let client: URLSessionClient
private let store: ApolloStore
private let shouldInvalidateClientOnDeinit: Bool
public init(client: URLSessionClient = URLSessionClient(),
shouldInvalidateClientOnDeinit: Bool = true,
store: ApolloStore) {
self.client = client
self.shouldInvalidateClientOnDeinit = shouldInvalidateClientOnDeinit
self.store = store
}
deinit {
if self.shouldInvalidateClientOnDeinit {
self.client.invalidate()
}
}
open func interceptors<Operation: GraphQLOperation>(for operation: Operation) -> [ApolloInterceptor] {
return [
MaxRetryInterceptor(),
CacheReadInterceptor(store: self.store),
RequestLoggingInterceptor(), // added logging interceptor
NetworkFetchInterceptor(client: self.client),
ResponseCodeInterceptor(),
JSONResponseParsingInterceptor(cacheKeyForObject: self.store.cacheKeyForObject),
AutomaticPersistedQueryInterceptor(),
CacheWriteInterceptor(store: self.store),
]
}
open func additionalErrorInterceptor<Operation: GraphQLOperation>(for operation: Operation) -> ApolloErrorInterceptor? {
return nil
}
}
class RequestLoggingInterceptor: ApolloInterceptor {
func interceptAsync<Operation: GraphQLOperation>(
chain: RequestChain,
request: HTTPRequest<Operation>,
response: HTTPResponse<Operation>?,
completion: #escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void) {
if let url = try? request.toURLRequest().url?.absoluteString.removingPercentEncoding {
if let variables = request.operation.variables {
print("\(request.operation.operationName) parameters: \(variables) \(url)")
} else {
print("\(request.operation.operationName) \(url)")
}
}
chain.proceedAsync(request: request, response: response, completion: completion)
}
}
I use the custom interceptor provider in the Request Chain Network Transport.
private(set) lazy var apolloClient: ApolloClient = {
let store = ApolloStore()
let interceptorProvider = InterceptorProviderWithLogging(store: store)
let requestChainTransport = RequestChainNetworkTransport(
interceptorProvider: interceptorProvider,
endpointURL: url,
additionalHeaders: [:],
autoPersistQueries: false,
requestBodyCreator: ApolloRequestBodyCreator(),
useGETForQueries: true,
useGETForPersistedQueryRetry: false
)
return ApolloClient(networkTransport: requestChainTransport, store: store)
}()
Extending GraphQLQuery provides access to the operation name, operation id, and variables, which can be used to build up the url. I also print out the operation name and variables for the query.
extension GraphQLQuery {
func printInfo() {
if let variables = self.variables?.JSONString {
let cleanedVariables = variables.replacingOccurrences(of: "\\", with: "")
print("GraphQL Query: \(self.operationName) \(variables))")
if let operationID = self.operationIdentifier {
let url = "\(GraphQLClient.shared.url)?extensions={\"persistedQuery\":{\"sha256Hash\":\"\(operationID)\",\"version\":1}}&id=\(operationID)&operationName=\(self.operationName)&variables=\(cleanedVariables)"
print("GraphQL URL", url)
}
} else {
print("GraphQL Query: \(self.operationName)")
if let operationID = self.operationIdentifier {
let url = "\(GraphQLClient.shared.url)?extensions={\"persistedQuery\":{\"sha256Hash\":\"\(operationID)\",\"version\":1}}&id=\(operationID)&operationName=\(self.operationName)"
print("GraphQL URL", url)
}
}
}
}
Usage:
let standingsQuery = GetStandingsForSportQuery(sportID: sportIDInt, season: season)
standingsQuery.printInfo()
Example output:
GraphQL Query: getStandingsForSport {"sportID":7,"season":"2020"})
GraphQL URL: https://api.company.com/graphql?extensions={"persistedQuery":{"sha256Hash":"932b414fdadb641f95659d6c61aa29d6d6b0ccf1fa704a0ace751187b90b8cac","version":1}}&id=932b414fdadb641f95659d6c61aa29d6d6b0ccf1fa704a0ace751187b90b8cac&operationName=getStandingsForSport&variables={"sportID":1,"season":"2020"}
The url format in this example may not be typical as we're using persisted queries. I used Charles proxy to see the actual url being sent so I'd know the format.
You could also extend GraphQLOperation instead of GraphQLQuery to get this same info, which would also support mutations and subscriptions.

TDD(iOS) SUT fails when I don't make a local variable and run some operation

I have written a test and in my test a router spy routes to the next logic of my app. using start() function. When I directly call start function on my makeSUT function test fails. However when I make a local SUT variable and then call start function test passes. I think it might be that my escaping closure self gets nil or something however I am not sure. Please do let me know so I can understand what's happening under the hood
Here's the code.
class FlowTest: XCTestCase {
var router: RouterSpy!
func makeSUT(questions: [String]) -> Flow {
router = RouterSpy()
return Flow(questions: questions, router: router);
}
// THIS Test FAILS WHEN I DO makeSUT(questions: ["First One", "Second", "Third"]).start()
func test_startAndAnswerFirstAndSecondQuestion_withTwoQuestions_routeToSecondAndThirdQuestion(){
let sut = makeSUT(questions: ["First One", "Second", "Third"])
sut.start()
router.answerCallback("A1")
router.answerCallback("A2")
XCTAssertEqual(router.routedQuestions, ["First One", "Second","Third"])
}
class RouterSpy: Router {
var routedQuestions: [String] = []
var answerCallback: Router.AnswerCallback = {_ in}
func routeTo(question: String,answerCallback: #escaping Router.AnswerCallback) {
routedQuestions.append(question)
self.answerCallback = answerCallback
}
}
}
Here's my Flow class and my Router Protocol
protocol Router {
typealias AnswerCallback = (String) -> Void
func routeTo(question: String, answerCallback: #escaping AnswerCallback)
}
class Flow {
private let router: Router
private let questions: [String]
init(questions: [String] = [], router: Router) {
self.router = router
self.questions = questions
}
func start() {
if let firstQuestion = questions.first {
self.router.routeTo(question: firstQuestion, answerCallback: routeNext(from : firstQuestion))
}
}
private func routeNext(from question: String) -> Router.AnswerCallback {
return { [weak self] _ in
guard let self = self else {return}
if let index = self.questions.firstIndex(of: question){
if index + 1 < self.questions.count {
let nextQuestion = self.questions[index + 1]
self.router.routeTo(question: nextQuestion, answerCallback: self.routeNext(from : nextQuestion))
}
}
}
}
}
When you're chaining the two functions together to create sut, you're not actually creating an instance of the Flow class. Then when you're calling answerCallback() in your test, there's no instance of Flow and therefore no questions Array to pass to the function.
When you initialise sut separately by calling makeSut(questions:) before then calling start() you are actually creating an instance of the Flow class as sut, and as such router does have a questions Array to access.

PromiseKit - Pass previous result to next promise in chain

Im trying to follow the Documentation to pass the result of a promise to the next promise
https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#saving-previous-results
I keep getting
Contextual closure type '(_) throws -> _' expects 1 argument, but 2 were used in closure body
Here my Code
class func loginInOrSignup() -> Promise<PFUser> {
let lm = LoginManager()
lm.logOut()
return logInInBackground().then{user in
if user.isNew{
return getUserInfo().map{ ($0, user) }
}
else{
user.fetchIfNeededInBackground()
return Promise.value(user)
}
}.then{(userInfo, user) -> Promise<PFUser> in
let name = userInfo["name"] as! String
if let email = userInfo["email"] as? String {
user.email = email
}
let username = G8FacebookLogin.generateSuggestedUsername(name: name)
user.username = username
return Promise.value(user)
}
}
private class func logInInBackground() -> Promise<PFUser>{
return Promise {seal in
PFFacebookUtils.logInInBackground(withReadPermissions: ["public_profile", "email"]){user, error in
guard let user = user else {
seal.reject(error ?? AuthError.msg(reason: "Some FB Error"))
return
}
seal.fulfill(user)
}
}
}
private class func getUserInfo() -> Promise<Dictionary<String,Any>> {
return Promise {seal in
G8FacebookLogin.getUserInfo { (userDict, error) in
guard let userInfo = userDict else{
PFUser.logOut()
seal.reject(AuthError.loginFailed(reason: "no user Info retrieved"))
return
}
seal.fulfill(userInfo)
}
}
}
It's pretty much the same as the Snippet code in the documentation. I don't understand what is the right way to do it.

How to handle SignalProducer with ReactiveSwift and Firebase asynchronous method calls?

I am working on an iOS App with Swift 3 using ReactiveSwift 1.1.1, the MVVM + Flow Coordinator pattern and Firebase as a backend. I only recently started to adapt to FRP and I am still trying to figure out how to integrate new functionalities into my existing code base.
For instance, my model uses a asynchronous method from Firebase to download thumbnails from the web and I want to provide a SignalProducer<Content, NoError> to subscribe from my ViewModel classes and observe, if thumbnails have been downloaded, which then updates the UI.
// field to be used from the view-models to observe
public let thumbnailContentSignalProducer = SignalProducer<Content, NoError> { (observer, disposable) in
// TODO: send next content via completion below
}
// thumbnail download method
public func findThumbnail(bucketId: String, contentId: String) {
guard let userId = userService.getCurrentUserId() else {
debugPring("Error id")
return
}
let ref = self.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
debugPrint("Error download")
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
// TODO: emit signal with content
// How to send the content via the SignalProducer above?
})
}
I have also tried something similar with Signal<Content, NoError>, whereas I used the Signal<Content, NoError>.pipe() method to receive a (observer, disposable) tuple and I saved the observer as a private global field to access it form the Firebase callback.
Questions:
Is this the right approach or am I missing something?
How do I emit the content object on completion?
UPDATE:
After some hours of pain, I found out how to design the SingalProducer to emit signals and to subscribe from the ViewModels.
Maybe the following code snippet will help also others:
// model protocol
import ReactiveSwift
import enum Result.NoError
public protocol ContentService {
func findThumbnail(bucketId: String, contentId: String)
var thumbnailContentProducer: SignalProducer<Content, NoError> { get }
}
// model implementation using firebase
import Firebase
import FirebaseStorage
import ReactiveSwift
public class FirebaseContentService: ContentService {
// other fields, etc.
// ...
private var thumbnailContentObserver: Observer<Content, NoError>?
private var thumbnailContentSignalProducer: SignalProducer<Content, NoError>?
var thumbnailContentProducer: SignalProducer<Content, NoError> {
return thumbnailContentSignalProducer!
}
init() {
thumbnailContentSignalProducer = SignalProducer<Content, NoError> { (observer, disposable) in
self.thumbnailContentObserver = observer
}
}
func findThumbnail(bucketId: String, contentId: String) {
guard let userId = userService.getCurrentUserId() else {
// TODO handle error
return
}
let ref = self.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
// TODO handle error
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
// emit signal
self.thumbnailContentObserver?.send(value: content)
})
}
}
// usage from a ViewModel
contentService.thumbnailContentProducer
.startWithValues { content in
self.contents.append(content)
}
Maybe someone can verify the code above and say that this is the right way to do it.
I think you were on the right path when you were looking at using Signal with pipe. The key point is that you need to create a new SignalProducer for each thumbnail request, and you need a way to combine all of those requests into one resulting signal. I was thinking something like this (note this is untested code, but it should get the idea across):
class FirebaseContentService {
// userService and storageThumbnail defined here
}
extension FirebaseContentService: ReactiveExtensionsProvider { }
extension Reactive where Base: FirebaseContentService {
private func getThumbnailContentSignalProducer(bucketId: String, contentId: String) -> SignalProducer<Content, ContentError> {
return SignalProducer<Content, ContentError> { (observer, disposable) in
guard let userId = self.base.userService.getCurrentUserId() else {
observer.send(error: ContentError.invalidUserLogin)
return
}
let ref = self.base.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
observer.send(error: ContentError.contentNotFound)
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
observer.send(value: content)
observer.sendCompleted()
})
}
}
}
class ThumbnailProvider {
public let thumbnailSignal: Signal<Content, NoError>
private let input: Observer<(bucketId: String, contentId: String), NoError>
init(contentService: FirebaseContentService) {
let (signal, observer) = Signal<(bucketId: String, contentId: String), NoError>.pipe()
self.input = observer
self.thumbnailSignal = signal
.flatMap(.merge) { param in
return contentService.reactive.getThumbnailContentSignalProducer(bucketId: param.bucketId, contentId: param.contentId)
.flatMapError { error in
debugPrint("Error download")
return SignalProducer.empty
}
}
}
public func findThumbnail(bucketId: String, contentId: String) {
input.send(value: (bucketId: bucketId, contentId: contentId))
}
}
Using ReactiveExtensionsProvider like this is the idiomatic way of adding reactive APIs to existing functionality via a reactive property.
The actual requesting code is confined to getThumbnailContentSignalProducer which creates a SignalProducer for each request. Note that errors are passed along here, and the handling and conversion to NoError happens later.
findThumbnails just takes a bucketId and contentId and sends it through the input observable.
The construction of thumbnailSignal in init is where the magic happens. Each input, which is a tuple containing a bucketId and contentId, is converted into a request via flatMap. Note that the .merge strategy means the thumbnails are sent as soon as possible in whatever order the requests complete. You can use .concat if you want to ensure that the thumbnails are returned in the same order they were requested.
The flatMapError is where the potential errors get handled. In this case it's just printing "Error download" and doing nothing else.

Resources