Anybody can help me with this? I have this small code:
getUserDetailsApi().flatMap(){users in
return getScoreApi(users[0])
}.subscribe(
onCompleted: {
print("Done")
},
onError: {
// which of the two APIs get an error?
})
I call two APIs here, in the getUserDetailsApi I want to invoke an error when it failed to get the user details or something went wrong and skip the getScoreApi. Same on the getScoreApi if it fails to get the score of the user it will throw a different error.
is there a way I can throw the said errors on flatMap()?. Note that the two observable must be execute in sequence order and these errors has different message
You should throw the error in getUserDetailsApi() and getScoreApi().
Example:
func getUserDetailsApi() -> Observable<[User]> {
return Observable.create { observer in
// Your api call
// ...
// Probably you get the users array or an error.
if (error) {
observer.onError(YourError.UserDetailsError) // <- Your error
} else {
observer.onNext(users)
observer.onCompleted()
}
return Disposables.create {
// your dispose
}
}
}
And the same for getScoreApi(). Then, if one of them fails, the flatMap will fail.
getUserDetailsApi().flatMap(){users in
return getScoreApi(users[0])
}.subscribe(
onCompleted: {
print("Done")
},
onError: {
switch error{
case .userDetailsError:
// ...
case .otherError:
// ...
}
})
Related
I am calling Future like this:
//main_bloc.dart
...
getData() {
print("getting data");
repository.getDataFromServer().then((result) {
_handleResult(result);
}).catchError((e) {
_handleError(e);
});
}
In runtime, when there is exception from the repository, it will be catched in the catchError and forward properly.
However, when i do unit testing to that part of code like this:
//prepare
when(mockRepository.getDataFromServer()).thenThrow(PlatformException(code: "400", message: "Error", details: ""));
//act
bloc.getData();
await untilCalled(mockRepository.getDataFromServer());
//assert
verify(mockRepository.getDataFromServer());
The catchError method not called and the test is failed due to unHandled exception.
What i am doing wrong?
Your code expects to catch an error from a returned Future. Your mock throws an exception immediately (synchronously) when it is invoked; it never returns a Future.
I think that you instead would need to do:
when(repository.getDataFromServer()).thenAnswer((_) => Future.error(
PlatformException(code: "400", message: "Error", details: "")));
A simpler (and more robust) change would be to use try-catch in your code instead of Future.catchError:
Future<void> getData() async {
print("getting data");
try {
_handleResult(await repository.getDataFromServer());
} catch (e) {
_handleError(e);
}
}
i Already can get Apollo Client errors:
import {onError} from 'apollo-link-error'
function createLink() {
return ApolloLink.from([
createErrorLink(),
createHttpLink(),
])
}
function createErrorLink() {
return onError(function ({graphQLErrors, networkError}) {
if (graphQLErrors) {
graphQLErrors.map(({message}) => {
console.log(`[GraphQL error]`, message)
})
}
if (networkError) {
console.log(`[Network error]`, networkError)
}
})
}
Yes, error handlers works, but how i can PREVENT promise rejection?
const errorLink = onError(({networkError, graphQLErrors, response}) => {
// DO ANYTHING
// Stop error propagation to next Links
response.errors = null
})
HttpLink not see errors end not reject promise =)
I have Ember code where the backend API calls are abstracted into a
separate service. This service uses ember-ajax library for making
backend calls.
This service sets up the common headers, handles the
timeout errors, and 4xx/5xx errors. And anything else like 422
(validation errors) are left to be handled by the calling code.
-
getCustomerProfile (authenticationToken) {
const backendService = this.get('callBackendService');
return backendService.callEndpoint(GET_METHOD,
getCustomerProfileAPI.url,
{'X-Auth-Token': authenticationToken}).then((customerProfileData) => {
if (!backendService.get('didAnybodyWin') && customerProfileData) {
backendService.set('didAnybodyWin', true);
return customerProfileData.profiles[0];
}
}).catch((error) => {
if (isInvalidError(error)) {
if (!backendService.get('didAnybodyWin')) {
backendService.set('didAnybodyWin', true);
backendService.transitionToErrorPage();
return;
}
}
});
}
and the call-backend-service looks like this
callEndpoint (httpVerb, endPoint, headersParameter, data = {},
timeoutInMillisecs = backendCallTimeoutInMilliseconds) {
const headersConst = {
'Content-Type': 'application/vnd.api+json',
'Accept': 'application/vnd.api+json',
'Brand': 'abc'
};
var headers = Ember.assign(headersParameter, headersConst);
var promiseFunctionWrapper;
this.set('didAnybodyWin', false);
if (httpVerb.toUpperCase() === GET_METHOD) {
Ember.Logger.warn('hit GET Method');
promiseFunctionWrapper = () => {
return this.get('ajax').request(endPoint, {headers});
};
} else if (httpVerb.toUpperCase() === POST_METHOD) {
Ember.Logger.warn('hit POST Method');
promiseFunctionWrapper = () => {
return this.get('ajax').post(endPoint, {data: data, headers: headers});
};
}
return RSVP.Promise.race([promiseFunctionWrapper(), this.delay(timeoutInMillisecs).then(() => {
if (!this.get('didAnybodyWin')) {
this.set('didAnybodyWin', true);
Ember.Logger.error('timeout of %s happened when calling the endpoint %s', backendCallTimeoutInMilliseconds, endPoint);
this.transitionToErrorPage();
return;
}
})]).catch((error) => {
if (!this.get('didAnybodyWin')) {
if (isTimeoutError(error)) {
this.set('didAnybodyWin', true);
Ember.Logger.warn('callBackEndService: isTimeoutError(error) condition is true');
this.transitionToErrorPage();
return;
} else if (isAjaxError(error) && !isInvalidError(error)) { //handles all errors except http 422 (inValid request)
this.set('didAnybodyWin', true);
Ember.Logger.warn('callBackEndService: isAjaxError(error) && !isInvalidError(error) [[ non timeout error]] condition is true');
this.transitionToErrorPage();
return;
} else {
throw error; // to be caught by the caller
}
}
});
},
The callEndpoint does a RSVP.Promise.race call to make sure the called backend API comes back before a timeout happens. It runs two promises and whichever resolves first is the one that wins. didAnybodyWin is the flag that guards both the promises from getting executed.
Up to this part is all fine.
But this didAnybodyWin becomes the shared state of this call-backend-service because it has to convey back to the caller whether it ran the default set of then or catch blocks or does it expect the caller to run its then/catch blocks.
The problem is when model() hook is run, I am doing
RSVP.all {
backendAPICall1(),
backendAPICall2(),
backendAPICAll3()
}
This RSVP.all is going to execute all 3 calls one after another, so they will hit the call-backend-service in an interleaved fashion and hence run the risk of stepping over each other (when it comes to the didAnybodyWin shared state).
How can this situation be avoided ?
Is there any other better way for the callee to signal to the caller whether or not its supposed to do something with the returned promise.
I'm using expectSaga ('redux-saga-test-plan') to test one of my sagas and I'm wondering how to test multiple calls made within the same saga.
Sagas.js
export function* fetchSomething(arg){
const response = yield call(executeFetch, arg);
if(response.status === 200){
// trigger success action
} else if (response.status >= 400){
const errResp = yield response.json();
const errorCode = yield call(sharedUtilToExtractErrors, errResp);
yield put(
{ type: 'FETCH_FAILED', errorMessage: UI_ERR_MSG, errorCode }
);
}
}
Unit test
import { expectSaga } from 'redux-saga-test-plan';
describe('fetchSomething', () => {
// positive paths
// ..
// negative paths
it('fetches something and with status code 400 triggers FETCH_FAILED with error message and extracted error code', () => {
const serverError = { message: 'BANG BANG BABY!' };
const koResponse = new Response(
JSON.stringify(serverError),
{ status: 400, headers: { 'Content-type': 'application/json' } }
);
return expectSaga(fetchSomething)
.provide(
{
call: () => koResponse,
call: () => serverError.message,
}
)
.put({
type: 'FETCH_FAILED', errorMessage: UI_ERR_MSG, serverError.message
})
.run();
})
})
Clearly having the "call" attribute twice in the same object passed in to provide() doesn't work but also calling provide() twice doesn't do the trick. Any suggestions?
Thanks
This is how you can provide multiple calls according to the documentation:
.provide([ // this external array is actually optional
[call(executeFetch, arg), koResponse],
[call(sharedUtilToExtractErrors, serverError), serverError.message],
])
or if you're lazy and don't want to specify the arguments:
import * as matchers from 'redux-saga-test-plan/matchers';
.provide(
[matchers.call.fn(executeFetch), koResponse],
[matchers.call.fn(sharedUtilToExtractErrrors), serverError.message],
)
Neither of these two worked for me though as for some reason it was not mocking out the dependencies and still calling them caused errors.
I solved using a dynamic provider:
.provide({
// select(effect, next) { return 'something-for-a-selector' },
call(effect) {
switch(effect.fn.constructor.name) {
case executeFetch.constructor.name: return koResponse;
case sharedUtilToExtractErrors.constructor.name: return serverError.message;
default: throw new Error('Unknown function called in test');
}
}
})
I have 2 simple methods that abstract reading and writing to localStorage:
_readLocalStorage: function(key) {
if (window.localStorage && window.localStorage.getItem(key)) {
return JSON.parse(window.localStorage.getItem(key));
} else {
throw new Error('Could not read from localStorage');
}
},
_writeLocalStorage: function(key, data) {
try {
window.localStorage.setItem(key, JSON.stringify(data));
} catch (e) {
throw new Error('Could not write to localStorage');
}
},
Obviously, stubbing window.localStorage.getItem/setItem is simple. But what about the case where localStorage is undefined?
I've tried caching/unhinging window.localStorage (the second assertion):
describe('#_readLocalStorage', function() {
it('should read from localStorage', function() {
// set up
var stub1 = sinon.stub(window.localStorage, 'getItem')
.returns('{"foo": "bar"}');
// run unit
var result = service._readLocalStorage('foo');
// verify expectations
expect(result)
.to.eql({foo: 'bar'});
// tear down
stub1.restore();
});
it('should throw an error if localStorage is undefined', function() {
// set up
var cachedLocalStorage = window.localStorage;
window.localStorage = undefined;
// run unit/verify expectations
expect(service._readLocalStorage('foo'))
.to.throw(new Error('Could not write to localStorage'));
// tear down
window.localStorage = cachedLocalStorage;
});
});
This does not work however. Mocha/Chai seem not to catch the thrown error.
I've looked around a bit but can't find any way to handle this.
Your expect should be
expect(service._readLocalStorage.bind(service, 'foo'))
.to.throw(new Error('Could not write to localStorage'));
The way you have it you code calls service._readLocalStorage('foo') before expect is called. So it raises an exception that expect cannot handle. What expect needs to be able to deal with exceptions is a function that expect itself will call. Using service._readLocalStorage.bind(service, 'foo') creates a new function that when called without arguments (as expect does) will be equivalent to calling service._readLocalStorage('foo').
There's another problem with your test: your cleanup code will never execute. The assertion libraries report problems by raising JavaScript exceptions. So any code that follows a failed exception won't run unless the exception is specially handled. You could do:
it('should throw an error if localStorage is undefined', function() {
// set up
var cachedLocalStorage = window.localStorage;
window.localStorage = undefined;
// run unit/verify expectations
try {
expect(...)...;
expect(...)...;
...
}
finally {
// tear down
window.localStorage = cachedLocalStorage;
}
});
For more complex cases, you should use before, beforeEach, after, afterEach for setup and teardown.