Unit testing with FakeAsync in Dart - unit-testing

I'm kind of new to unit testing, and I am writing a unit test which uses the quiver package for faking async execution. The function I am using looks something like this:
import 'dart:async';
import 'package:quiver/testing/async.dart';
import 'package:rxdart/rxdart.dart';
import 'package:test/test.dart' as tester;
testStream<MyType>(
String message, {
Iterable expecting,
Future<void> Function(BehaviorSubject) action,
Duration waitTime,
int skip = 1,
}) {
tester.test(message, () {
final _stream = BehaviorSubject<MyType>();
FakeAsync().run((async) async {
final List<MyType> _results = <MyType>[];
final _subscription = _stream.skip(skip).listen(_results.add);
await action?.call(_stream);
if (waitTime != null) async.elapse(waitTime);
_subscription.cancel();
if (expecting != null) {
tester.expect(_results, expecting);
}
});
});
}
It works well on most tests I wrote, but I am having problems with a specific example due to the lack of knowledge. In the particular case I pass the action which adds a delayed future to the stream similarly like this:
testStream<int>(
'Just a basic message',
expecting: [1],
action: (stream) async =>
Future.delayed(Duration(days: 99), () => stream.add(1)),
waitTime: Duration(seconds: 1),
);
If I understood it right, this test should fail, however when I run it, it passes. What am I doing wrong here?
Then, what should I do to make it execute the action first and then elapse the timer after the action has been called without waiting for it?

Related

Is there a solution to asynchronously wait for C++ data on Flutter's invokeMethod?

Currently, this is how I read from C++ using Flutter:
final Uint8List result = await platform.invokeMethod(Common.MESSAGE_METHOD, {"message": buffer});
It is handled by Kotlin like this:
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == MESSAGE_METHOD) {
val message: ByteArray? = call.argument<ByteArray>("message")
//... //response = Read something FROM C++
result.success(response)
Since this happens in the main thread, if I take too much time to answer, I make Flutter's UI slow.
Is there a solution to get C++ data in an async way?
I know that Flutter has support for event channels to send data back from C++ to Flutter. But what about just requesting the data on the Flutter side and waiting for it to arrive in a Future, so I can have lots of widgets inside a FutureBuilder that resolves to something when ready?
If reading something from C++ is a heavy process, You can use AsysncTask to perform it in the background for android.
internal class HeavyMsgReader(var result: MethodChannel.Result) : AsyncTask<ByteArray?, Void?, String?>() {
override fun doInBackground(vararg message: ByteArray?): String {
//... //response = Read something FROM C++
return "response"
}
override fun onPostExecute(response: String?) {
result.success(response)
}
}
Calling async task:
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == MESSAGE_METHOD) {
val message: ByteArray? = call.argument<ByteArray>("message")
HeavyMsgReader(result).execute(message);
Hopefully this will work
import 'dart:async';
Future<Uint8List> fetchData(buffer) async {
final Uint8List result = await platform.invokeMethod(Common.MESSAGE_METHOD, {"message": buffer});
return result;
}
And just call it, like this
fetchData(buffer).then((result) => {
print(result)
}).catchError(print);
Proof that its working:
import 'dart:async';
Future<String> fetchUserOrder() async {
await Future.delayed(Duration(seconds: 5));
return 'Callback!';
}
Future<void> main() async {
fetchUserOrder().then((result) => {
print(result)
}).catchError(print);
while(true){
print('main_thread_running');
await Future.delayed(Duration(seconds: 1));
}
}
output:
main_thread_running
main_thread_running
main_thread_running
main_thread_running
main_thread_running
Callback!
main_thread_running
main_thread_running
...

Method (with optional parameter) mocked with mockito return null on unit test. Dart Flutter

Im five hours trying solve it, doesn't work nothing. I wanna test bloc class that use stream and sink data after load data from internet. The use case test work well but the bloc class was a huge headache today, this already work on app, but the test I really dont know how to solve
file bloc.dart
class Bloc {
final UseCase _useCase;
Bloc(this._useCase);
final _controller = StreamController.broadcast();
Stream get stream => _controller.stream;
doSomething() async {
ResponseModel responseModel = await _useCase.call();
_controller.sink.add(responseModel);//<-- I would like test this
}
dispose() {
_controller.close();
}
}
This is the unit test class bloc_test.dart
class UseCaseMock extends Mock implements UseCase {}
main() {
UseCase useCase;
Bloc bloc;
setUp(() async {
useCase = UseCaseMock();
bloc = Bloc(useCase);
});
tearDown(() {
bloc.dispose();
});
group('Test Bloc', () {
test('load stuff must sink Response ', () async {
when(useCase.call())
.thenAnswer((_) async => ResponseModel('id','name'));
//FIRST I TRY It, DOESNT WORK
// await expectLater( bloc.stream, emits(isA<ResponseModel>()));
bloc.stream.listen((response) {
//print(response) <-----return null I THINK HERE IS THE PROBLEM
expect(response, isA<ResponseModel>());
});
await bloc.doSomething();
});
});
}
Please, would you know how to solve it? Thanks
Solved here
Solved! my usecase has a method "call" with an optional parameter
Future<ResponseModel> call( {String value });
I was mocking wrong as below
when(useCase.call()) <----------------here is the error
.thenAnswer((_) async => ResponseModel('id','name'));
Inside true Bloc class(not a pseudocode that I had posted) I execute a usecase using the parameter
// this is the true doSomething() method on my app
loadCoupons(String storeId) async {
final result = await searchCouponUseCase(storeId: storeId);
_controller.sink.add(result);
}
And the solution is:
Mock using optional parameter(if it will be called)! I was testing without parameter because when putting "any" inside the test like a when( useCase.call( any))... not compile.
when(useCase.call( value: '' )) <-----solved
.thenAnswer((_) async => ResponseModel('id','name'));
You can run your async operation before using expectLater for assertion. Also remove the await from bloc.doSomething() as it times out otherwise.
This will work.
test('load stuff must sink Response ', () async {
when(useCase.call())
.thenAnswer((_) async => ResponseModel('id','name'));
bloc.doSomething();
await expectLater(bloc.stream, emits(isA<Response>()));
});

How can I unit test a retryWhen operator in rxjs?

I am attempting to unit test a custom RxJS operator. The operator is very simple, it uses RetryWhen to retry a failed HTTP request, but has a delay and will only retry when the HTTP Error is in the 500 range. Using jasmine, and this is in an Angular application.
I've looked at this:
rxjs unit test with retryWhen
Unfortunately, updating the SpyOn call doesn't seem to change the returned observable on successive retries. Each time it retries it is retrying with the original spyon Value.
I have also looked at a bunch of rxjs marble examples, none of which seem to work. I am not sure it is possible to use rxjs marbles here, because (AFAIK) there is no way to simulate a situation where you first submit an errored observable, then submit a successful observable on subsequent tries.
The code is basically a clone of this:
https://blog.angularindepth.com/retry-failed-http-requests-in-angular-f5959d486294
export function delayedRetry(delayMS: number, maxRetry) {
let retries = maxRetry;
return (src: Observable<any>) =>
src.pipe(
retryWhen((errors: Observable<any>) => errors.pipe(
delay(delayMS),
mergeMap(error =>
(retries-- > 0 && error.status >= 500) ? of(error) : throwError(error))
))
);
}
I would like to be able to demonstrate that it can subscribe to an observable that returns an error on the first attempt, but then returns a successful response. The end subscription should show whatever success value the observable emits.
Thank you in advance for any insights.
try use this observable as source observable to test
const source = (called,successAt)=>{
return defer(()=>{
if(called<successAt){
called++
return throwError({status:500})
}
else return of(true)
})
}
test
this.delayedRetry(1000,3)(source(0,3)).subscribe()
To test the retry functionality, you need a observable which emits different events each time you call it. For example:
let alreadyCalled = false;
const spy = spyOn<any>(TestBed.inject(MyService), 'getObservable').and.returnValue(
new Observable((observer) => {
if (alreadyCalled) {
observer.next(message);
}
alreadyCalled = true;
observer.error('error message');
})
);
This observable will emit an error first and after that a next event.
You can check, if your observable got the message like this:
it('should retry on error', async(done) => {
let alreadyCalled = false;
const spy = spyOn<any>(TestBed.inject(MyDependencyService), 'getObservable').and.returnValue(
new Observable((observer) => {
if (alreadyCalled) {
observer.next(message);
}
alreadyCalled = true;
observer.error('error message');
})
);
const observer = {
next: (result) => {
expect(result.value).toBe(expectedResult);
done();
}
}
subscription = service.methodUnderTest(observer);
expect(spy).toHaveBeenCalledTimes(1);
}
Building on a previous answer I have been using this, which gives you more control over what's returned.
const source = (observables) => {
let count = 0;
return defer(() => {
return observables[count++];
});
};
Which can then be used like this
const obsA = source([
throwError({status: 500}),
of(1),
]);
Or it can then be used with rxjs marbles like
const obsA = source([
cold('--#', null, { status: 500 }),
cold('--(a|)', { a: 1 }),
]);

How to properly test functions that return Mongoose queries as Promises

I'm trying to write a basic unit test to work on the function below, but can't get it to work. How do I test that something like a proper npm-express response is returned?
I already looked at Using Sinon to stub chained Mongoose calls, https://codeutopia.net/blog/2016/06/10/mongoose-models-and-unit-tests-the-definitive-guide/, and Unit Test with Mongoose, but still can't figure it out. My current best guess, and the resulting error, is below the function to be tested. If possible, I don't want to use anything but Mocha, Sinon, and Chai.expect (i.e. not sinon-mongoose, chai-as-expected, etc.). Any other advice, like what else I can/should test here, is welcome. Thank you!
The function to be tested:
function testGetOneProfile(user_id, res) {
Profiles
.findOne(user_id)
.exec()
.then( (profile) => {
let name = profile.user_name,
skills = profile.skills.join('\n'),
data = { 'name': name, 'skills': skills };
return res
.status(200)
.send(data);
})
.catch( (err) => console.log('Error:', err));
}
My current best-guess unit test:
const mongoose = require('mongoose'),
sinon = require('sinon'),
chai = require('chai'),
expect = chai.expect,
Profile = require('../models/profileModel'),
foo = require('../bin/foo');
mongoose.Promise = global.Promise;
describe('testGetOneProfile', function() {
beforeEach( function() {
sinon.stub(Profile, 'findOne');
});
afterEach( function() {
Profile.findOne.restore();
});
it('should send a response', function() {
let mock_user_id = 'U5YEHNYBS';
let expectedModel = {
user_id: 'U5YEHNYBS',
user_name: 'gus',
skills: [ 'JavaScript', 'Node.js', 'Java', 'Fitness', 'Riding', 'backend']
};
let expectedResponse = {
'name': 'gus',
'skills': 'JavaScript, Node.js, Java, Fitness, Riding, backend'
};
let res = {
send: sinon.stub(),
status: sinon.stub()
};
sinon.stub(mongoose.Query.prototype, 'exec').yields(null, expectedResponse);
Profile.findOne.returns(expectedModel);
foo.testGetOneProfile(mock_user_id, res);
sinon.assert.calledWith(res.send, expectedResponse);
});
});
The test message:
1) testGetOneProfile should send a response:
TypeError: Profiles.findOne(...).exec is not a function
at Object.testGetOneProfile (bin\foo.js:187:10)
at Context.<anonymous> (test\foo.test.js:99:12)
This is a bit of a tricky scenario. The problem here is that the findOne stub in your test returns the model object - instead, it needs to return an object which contains a property exec which in turn is a promise-returning function that finally resolves into the model value... yeah, as mentioned, it's a bit tricky :)
Something like this:
const findOneResult = {
exec: sinon.stub().resolves(expectedModel)
}
Profile.findOne.returns(findOneResult);
You also need to have the status function on the response object return an object containing a send function
//if we set up the stub to return the res object
//it returns the necessary func
res.status.returns(res);
I think you shouldn't need to change anything else in the test and it might work like that. Note that you sinon 2.0 or newer for the resolves function to exist on the stub (or you can use sinon-as-promised with sinon 1.x)
This post goes into a bit more detail on how you can deal with complex objects like that:
https://codeutopia.net/blog/2016/05/23/sinon-js-quick-tip-how-to-stubmock-complex-objects-such-as-dom-objects/

How to mock DialogService.open(...).whenClosed(...) with Jasmine?

We have some TypeScript code using the Aurelia framework and Dialog plugin that we are trying to test with Jasmine, but can't work out how to do properly.
This is the source function:
openDialog(action: string) {
this._dialogService.open({ viewModel: AddAccountWizard })
.whenClosed(result => {
if (!result.wasCancelled && result.output) {
const step = this.steps.find((i) => i.action === action);
if (step) {
step.isCompleted = true;
}
}
});
}
We can create a DialogService spy, and verify the open method easily - but we can't work out how to make the spy invoke the whenClosed method with a mocked result parameter so that we can then assert that the step is completed.
This is the current Jasmine code:
it("opens a dialog when clicking on incomplete bank account", async done => {
// arrange
arrangeMemberVerificationStatus();
await component.create(bootstrap);
const vm = component.viewModel as GettingStartedCustomElement;
dialogService.open.and.callFake(() => {
return { whenClosed: () => Promise.resolve({})};
});
// act
$(".link, .-arrow")[0].click();
// assert
expect(dialogService.open).toHaveBeenCalledWith({ viewModel: AddAccountWizard });
expect(vm.steps[2].isCompleted).toBeTruthy(); // FAILS
done();
});
We've just recently updated our DialogService and ran into the same issue, so we've made this primitive mock that suited our purposes so far. It's fairly limited and doesn't do well for mocking multiple calls with different results, but should work for your above case:
export class DialogServiceMock {
shouldCancelDialog = false;
leaveDialogOpen = false;
desiredOutput = {};
open = () => {
let result = { wasCancelled: this.shouldCancelDialog, output: this.desiredOutput };
let closedPromise = this.leaveDialogOpen ? new Promise((r) => { }) : Promise.resolve(result);
let resultPromise = Promise.resolve({ closeResult: closedPromise });
resultPromise.whenClosed = (callback) => {
return this.leaveDialogOpen ? new Promise((r) => { }) : Promise.resolve(typeof callback == "function" ? callback(result) : null);
};
return resultPromise;
};
}
This mock can be configured to test various responses, when a user cancels the dialog, and scenarios where the dialog is still open.
We haven't done e2e testing yet, so I don't know of a good way to make sure you wait until the .click() call finishes so you don't have a race condition between your expect()s and the whenClosed() logic, but I think you should be able to use the mock in the test like so:
it("opens a dialog when clicking on incomplete bank account", async done => {
// arrange
arrangeMemberVerificationStatus();
await component.create(bootstrap);
const vm = component.viewModel as GettingStartedCustomElement;
let mockDialogService = new MockDialogService();
vm.dialogService = mockDialogService; //Or however you're injecting the mock into the constructor; I don't see the code where you're doing that right now.
spyOn(mockDialogService, 'open').and.callThrough();
// act
$(".link, .-arrow")[0].click();
browser.sleep(100)//I'm guessing there's a better way to verify that it's finished with e2e testing, but the point is to make sure it finishes before we assert.
// assert
expect(mockDialogService.open).toHaveBeenCalledWith({ viewModel: AddAccountWizard });
expect(vm.steps[2].isCompleted).toBeTruthy(); // FAILS
done();
});