The unit test I write below doesn't work when there are 3 expect, how can I rewrite the unit test below:
test('400 bad response', () async {
when(
() => sampleClient.getTransaction(
'1'),
).thenThrow(DioError(
response: Response(
statusCode: 400,
data: badRepsonseJson,
requestOptions: RequestOptions(path: '')),
requestOptions: RequestOptions(path: '')));
final call = sampleService.getTransactionByHash(
'1');
expect(() => call, throwsA(TypeMatcher<SampleException>())); // Expect 1
try {
await sampleService.getTransactionByHash(
'1');
} on SampleException catch (e) {
expect(e.errorCode, badResponse.statusCode); // Expect 2
expect(e.message, badResponse.message); // Expect 3
}
});
final call = sampleService.getTransactionByHash(
'1');
expect(() => call, throwsA(TypeMatcher<SampleException>())); // Expect 1
I would not expect that expect to succeed. Invoking () => call just returns the already computed value of the call variable, which cannot fail.
If you instead expect sampleService.getTransactionByHash('1'); to throw an exception, then you need to invoke that in your callback to expect:
expect(
() => sampleService.getTransactionByHash('1'),
throwsA(TypeMatcher<SampleException>()),
);
Related
The promise on executesql on Ionic-native sqlite object is not working in NodeJS 16.
this.getTransactionDbConnection().executeSql(OfflineService.SQL_GET_METADATA, [key])
.then((data)
If I divide above call into 2 parts like below , then I am able to see both console statements
console.log(transDb); let sqlResponse =
transDb.executeSql(OfflineService.SQL_GET_METADATA, [key]);
console.log(sqlResponse); sqlResponse.then(data)``` --------- This line is not responding.
I do understand, I need to put async await and check.
But Observer will fail. That will be then a bigger change.
Same code works on NodeJS 12.
In package.json dependencies, I am using
Ionic-native/sqlite : ^4.7.0,
cordova-sqlite-storage : ^2.5.0
My complete code -
getMetadata(key: string): Observable<string> {
return Observable.create((observer: Observer<string>) => {
this.getTransactionDbConnection().executeSql(OfflineService.SQL_GET_METADATA, [key])
.then((data) => {
console.log('After executesql',JSON.stringify(data));
const metadataValue = data.rows.length > 0 ? data.rows.item(0).VALUE : '';
console.log('Get metadata.', key, metadataValue);
observer.next(metadataValue);
observer.complete();
}).catch(e => {
console.error('Error occurred while trying to get metadata.', e.message);
observer.error(e);
});
});
}
I have small app that execute requests to external service.
final app = Alfred();
app.post('/ctr', (req, res) async { // complex tender request
var data = await req.body;
await complexTendexAPIRequest(data as Map<String, dynamic>);
print('Hello World');
await res.json({'data': 'ok'});
});
Handler code:
complexTendexAPIRequest(Map<String, dynamic> data) async {
print('Request: $data');
try {
final response = await http.post(
Uri.parse(COMPLEX_URL),
headers: {'Content-Type': 'application/json', 'Authorization': 'bearer $ACCESS_TOKEN'},
body: json.encode(data)
);
if(response.statusCode == 200) {
var res = json.decode(response.body);
int latestId = res['id'];
String url = 'https://api.ru/v2/complex/status?id=$latestId';
stdout.write('Waiting for "complete" status from API: ');
Timer.periodic(Duration(seconds: 1), (timer) async {
final response = await http.get(
Uri.parse(url),
headers: {'Content-Type': 'application/json', 'Authorization': 'bearer $ACCESS_TOKEN'}
);
var data = json.decode(response.body);
if(data['status'] == 'completed') {
timer.cancel();
stdout.write('[DONE]');
stdout.write('\nFetching result: ');
String url = "https://api.ru/v2/complex/results?id=$latestId";
final response = await http.get(
Uri.parse(url),
headers: {'Content-Type': 'application/json', 'Authorization': 'bearer $ACCESS_TOKEN'}
);
stdout.write('[DONE]');
var data = prettyJson(json.decode(response.body));
await File('result.json').writeAsString(data.toString());
print("\nCreating dump of result: [DONE]");
}
});
}
else {
print('[ERROR] Wrong status code for complex request. StatusCode: ${response.statusCode}');
}
}
on SocketException catch(e) {
print('No Internet connection: $e');
} on TimeoutException catch(e) {
print('TenderAPI Timeout: $e');
} on Exception catch(e) {
print('Some unknown Exception: $e');
}
}
But output is very strange it's look like it's do not waiting complexTendexAPIRequest completion and go forward:
Waiting for "complete" status from API: Hello World
[DONE]
Fetching result: [DONE]
Creating dump of result: [DONE]
But should be:
Waiting for "complete" status from API: [DONE]
Fetching result: [DONE]
Creating dump of result: [DONE]
Hello World
I suppose that reason can be in Timer.periodic but how to fix it to get expected order and execution of:
print('Hello World');
await res.json({'data': 'ok'});
only after complexTendexAPIRequest completed.
upd: I rewrote code to while loop:
https://gist.github.com/bubnenkoff/fd6b4f0d7aeae7007680e7902fbdc1e9
it's seems that it's ok.
Alfred https://github.com/rknell/alfred
The problem is the Timer.periodic, as others have pointed out.
You do:
Timer.periodic(Duration(seconds: 1), (timer) async {
// do something ...
});
That sets up a timer, then immediately continues execution.
The timer triggers every second, calls the async callback (which returns a future that no-one ever waits for) and which does something which just might take longer than a second.
You can convert this to a normal loop, basically:
while (true) {
// do something ...
if (data['status'] == 'completed') {
// ...
break;
} else {
// You can choose your own delay here, doesn't have
// to be the same one every time.
await Future.delayed(const Duration(seconds: 1));
}
}
If you still want it to be timer driven, with fixed ticks, consider rewriting this as:
await for (var _ in Stream.periodic(const Duration(seconds: 1))) {
// do something ...
// Change `timer.cancel();` to a `break;` at the end of the block.
}
Here you create a stream which fires an event every second. Then you use an await for loop to wait for each steam event. If the thing you do inside the loop is asynchronous (does an await) then you are even ensured that the next stream event is delayed until the loop body is done, so you won't have two fetches running at the same time.
And if the code throws, the error will be caught by the surrounding try/catch, which I assume was intended, rather than being an uncaught error ending up in the Future that no-one listens to.
If you want to retain the Timer.periodic code, you can, but you need to do something extra to synchronize it with the async/await code around it (which only really understands futures and streams, not timers).
For example:
var timerDone = Completer();
Timer.periodic(const Duration(seconds: 1), (timer) async {
try {
// do something ...
// Add `timerDone.complete();` next to `timer.cancel()`.
} catch (e, s) {
timer.cancel();
timerDone.completeError(e, s);
}
});
await timerDone.future;
This code uses a Completer to complete a future and effectively bridge the gap between timers and futures that can be awaited (one of the listed uses of Completer in the documentation).
You may still risk the timer running concurrently if one step takes longer than a second.
Also, you can possibly use the retry package, if it's the same thing you want to retry until it works.
I have a function defined by:
static Future<http.Response> checkToken(token) async {
return await http.post("my-url", headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}, body: json.encode({
"token": token
})).timeout(const Duration (seconds:5), onTimeout : () => null);
}
I wonder if this function is identical to this function:
static Future<http.Response> checkToken(token) {
return http.post("my-url", headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}, body: json.encode({
"token": token
})).timeout(const Duration (seconds:5), onTimeout : () => null);
}
In the second definition I removed async / await part, cause on the web I found this statement:
Tip: If a function returns a Future, it’s considered asyncrounous; you do not need to mark the body of this function with async keyword. The async keyword is necessary only if you have used the await keyword in the body of your function.
Are these two functions identical?
It's true that the async keyword is not what makes a function asynchronous. The two versions of your function therefore are both asynchronous, and they're mostly equivalent but are subtly not identical.
The first version (which uses await) waits for a Future to complete and returns a new Future. The second version just returns the original Future directly.
Let's simplify the example:
final someFuture = Future.value(42);
Future<int> returnsFuture() => someFuture;
Future<int> awaitsFuture() async => await someFuture;
void main() {
final same = identical(returnsFuture(), awaitsFuture());
print('Identical Futures: $same'); // Prints: Identical Futures: false
}
In practice this usually shouldn't matter. Two cases where it might are:
Your code is somehow sensitive to specific Future instances.
await always yields, so the extra await could very slightly delay execution.
Since your asynchronously method does not contain the keyword await, then the two methods is indeed identical.
// didn't add node definitions because this problem occurs on initial data fetch
// user type
const userType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: globalIdField('User'),
email: { type: GraphQLString },
posts: {
type: postConnection,
args: connectionArgs,
// getUserPosts() function is below
resolve: (user, args) => connectionFromArray(getUserPosts(user.id), args),
},
}),
interfaces: [nodeInterface],
})
// post type
const postType = new GraphQLObjectType({
name: 'Post',
fields: () => ({
id: globalIdField('Post'),
title: { type: GraphQLString },
content: { type: GraphQLString },
}),
interfaces: [nodeInterface],
})
// connection type
const {connectionType: postConnection} =
connectionDefinitions({name: 'Post', nodeType: postType})
// Mongoose query on other file
exports.getUserPosts = (userid) => {
return new Promise((resolve, reject) => {
Post.find({'author': userid}).exec((err, res) => {
err ? reject(err) : resolve(res)
})
})
}
I get the following warning in browser console:
Server request for query App failed for the following reasons:
Cannot read property 'length' of undefined
_posts40BFVD:posts(first:10) {
^^^
That's the only information I got, there's no more errors or references. What could be the reason?
This code is from relay-starter-kit, I only replaced all the Widget code with Post. Everything is almost the same as in starter, therefore I think the cause is somewhere around the database code.
But I can't see the problem because getUserPosts() returns same structure: array of objects..
What was the problem?
resolve: (user, args) => connectionFromArray(getUserPosts(user.id), args)
getUserPosts() returned a promise (blocking code would probably be a bad idea) but there was no callback. What happened in my opinion was that connectionFromArray() continued executing the code but it didn't have the data from getUserPosts() yet, which caused the whole system to fail.
One possible solution
I use Babel and JavaScript "future features" anyway, therefore I decided to use async and await. But there still was a problem and getUserPosts() returned an empty array.
Then I discovered that if another function is called with await in a async function, that another function has to be async as well, otherwise all await-s fail. Here's my final solution that works:
// async function that makes db query
exports.getUserPosts = async (userId) => {
try {
// db query
const posts = await Post.find({author: args}).exec()
return posts
} catch (err) {
return err
}
}
// and resolve method in Schema, also async
resolve: async (user, args) => {
const posts = await getUserPosts(user._id)
return connectionFromArray(posts, args)
}
Im not still sure though if it's the best way. My logic tells me that I should use async as much as possible in Node but Im far from Node expert. I'll update the question when I know more about it.
I would be glad to know if there's a better or even a recommended way to deal with this database query situation using Relay.
The problem is with the resolve function of posts field.
resolve: (user, args) => connectionFromArray(getUserPosts(user.id), args),
First, getUserPosts is an asynchronous function which returns a promise. You have to take that into account while writing resolve function. Second, user.id is a global ID field generated for you by graphql-relay module's globalIdField helper function. That ID should be converted to local ID.
With relay-starter-kit, you can use async and await. The code will look like:
resolve: async (user, args) => {
let userId = fromGlobalId(user.id).id;
// convert this userId string to Mongo ID if needed
// userId = mongoose.Types.ObjectId(userId).
const posts = await getUserPosts(userId);
return connectionFromArray(posts, args),
},
In your async version of getUserPosts, the err object is returned in case of error.
exports.getUserPosts = async (userId) => {
try {
// db query
const posts = await Post.find({author: args}).exec()
return posts
} catch (err) {
return err
}
}
A good practice is to either re-throw the error or return an empty array.
graphql-relay provides a connectionFromPromisedArray function which waits until the promise resolves. resolve: (user, args) => connectionFromPromisedArray(getUserPosts(user.id), args), which probably is the most recommended/easiest way when dealing with promised connections.
If your user.id is a global ID field then you have to extract the real DB ID from the base64 string const { type, id } = fromGlobalId(user.id);.
or
resolve: (user, args) => connectionFromPromisedArray(getUserPosts(fromGlobalId(user.id).id), args),
In a app I work on which is using Relay and Mongoose I found it better to write my own connection implementation to handle pagination and filtering on the DB level rather than on the app level. I took a lot of inspiration from graffiti-mongoose when I wrote it.
Consider a function that does some exception handling based on the arguments passed:
List range(start, stop) {
if (start >= stop) {
throw new ArgumentError("start must be less than stop");
}
// remainder of function
}
How do I test that the right kind of exception is raised?
In this case, there are various ways to test the exception. To simply test that an unspecific exception is raised:
expect(() => range(5, 5), throwsException);
to test that the right type of exception is raised:
there are several predefined matchers for general purposes like throwsArgumentError, throwsRangeError, throwsUnsupportedError, etc.. for types for which no predefined matcher exists, you can use TypeMatcher<T>.
expect(() => range(5, 2), throwsA(TypeMatcher<IndexError>()));
to ensure that no exception is raised:
expect(() => range(5, 10), returnsNormally);
to test the exception type and exception message:
expect(() => range(5, 3),
throwsA(predicate((e) => e is ArgumentError && e.message == 'start must be less than stop')));
here is another way to do this:
expect(() => range(5, 3),
throwsA(allOf(isArgumentError, predicate((e) => e.message == 'start must be less than stop'))));
(Thanks to Graham Wheeler at Google for the last 2 solutions).
I like this approach:
test('when start > stop', () {
try {
range(5, 3);
} on ArgumentError catch(e) {
expect(e.message, 'start must be less than stop');
return;
}
throw new ExpectException("Expected ArgumentError");
});
As a more elegant solution to #Shailen Tuli's proposal, if you want to expect an error with a specific message, you can use having.
In this situation, you are looking for something like this:
expect(
() => range(5, 3),
throwsA(
isA<ArgumentError>().having(
(error) => error.message, // The feature you want to check.
'message', // The description of the feature.
'start must be less than stop', // The error message.
),
),
);
An exception is checked using throwsA with TypeMatcher.
Note: isInstanceOf is now deprecated.
List range(start, stop) {
if (start >= stop) {
throw new ArgumentError("start must be less than stop");
}
// remainder of function
}
test("check argument error", () {
expect(() => range(1, 2), throwsA(TypeMatcher<ArgumentError>()));
});
While the other answers are definitely valid, APIs like TypeMatcher<Type>() are deprecated now, and you have to use isA<TypeOfException>().
For instance, what was previously,
expect(() => range(5, 2), throwsA(TypeMatcher<IndexError>()));
Will now be
expect(() => range(5, 2), throwsA(isA<IndexError>()));
For simple exception testing, I prefer to use the static method API:
Expect.throws(
// test condition
(){
throw new Exception('code I expect to throw');
},
// exception object inspection
(err) => err is Exception
);
I used the following approach:
First you need to pass in your method as a lambda into the expect function:
expect(() => method(...), ...)
Secondly you need to use throwsA in combination with isInstanceOf.
throwsA makes sure that an exception is thrown, and with isInstanceOf you can check if the correct Exception was thrown.
Example for my unit test:
expect(() => parser.parse(raw), throwsA(isInstanceOf<FailedCRCCheck>()));
Hope this helps.