How can I test this async piece of code in Flutter using Mockito? - flutter

I am trying to write some test for an app, but I can't test values stored in a provider. I am relatively new to testing, so there might be something that I am doing wrong, but anyway. What I want to test is to verify is two values are not the same, which should be the expected behavior, but I just can't make it pass.
This is the code that I want to test:
class RSAKeysProvider {
KeyPair? _keyPair;
KeyPair? get keyPair => _keyPair;
set setKeyPair(KeyPair keyPair) => _keyPair = keyPair;
Future<void> generate(String bits) async {
var keyPair = await RSA.generate(int.parse(bits));
_keyPair = keyPair;
notifyListeners();
}
}
I need to first call the generate() function, which will set the keyPair to actual values, and then check if keyPair.publicKey is different than keyPair.privateKey, but it gives me an error when I try to call generate() with await inside a test.
This is what I have for now, but it doesn't work. The test breaks when it cames to the line "await rsaKeys.generate('2048'). What can I do to make it work? I know the condition is not checking if both are different, but it is just a placeholder, I can't make the code arrive there!
test('Public and private key should be different', () async {
final MockRSAKeysProvider rsaKeys = MockRSAKeysProvider();
when(rsaKeys.generate(any)).thenAnswer((value) async {
KeyPair keyPair = await RSA.generate(2048);
rsaKeys.setKeyPair = keyPair;
});
await rsaKeys.generate('2048');
expect(rsaKeys.keyPair?.privateKey, isNotNull);
expect(rsaKeys.keyPair?.publicKey, isNotNull);
});
When it arrives at "await rsaKeys.generate('2048'), it gives me this error:
Invalid argument(s): Failed to load dynamic library 'librsa_bridge.dylib': dlopen(librsa_bridge.dylib, 0x0001): tried: 'librsa_bridge.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OSlibrsa_bridge.dylib' (no such file), '/opt/homebrew/Caskroom/flutter/3.7.3/flutter/bin/cache/artifacts/engine/darwin-x64/./librsa_bridge.dylib' (no such file), '/opt/homebrew/Caskroom/flutter/3.7.3/flutter/bin/cache/artifacts/engine/darwin-x64/../../../librsa_bridge.dylib' (no such file), '/opt/homebrew/Caskroom/flutter/3.7.3/flutter/bin/cache/artifacts/engine/darwin-x64/Frameworks/librsa_bridge.dylib' (no such file), '/opt/homebrew/Caskroom/flutter/3.7.3/flutter/bin/cache/artifacts/engine/darwin-x64/./librsa_bridge.dylib' (no such file), '/opt/homebrew/Caskroom/flutter/3.7.3/flutter/bin/cache/artifacts/engine/darwin-x64/../../../librsa_bridge.dylib' (no such file), '/opt/homebrew/Caskroom/flutter/3.7.3/flutter/bin/cache/artifacts/engine/darwin-x64/Frameworks/librsa_bridge.dylib' (no such file), '/usr/lib/librsa_bridge.dylib' (no such file, not in dyld cache), 'librsa_bridge.dylib' (no such file), '/usr/lib/librsa_bridge.dylib' (no such file, not in dyld cache)
dart:ffi new DynamicLibrary.open
package:fast_rsa/bridge/binding.dart 117:33 Binding.openLib
package:fast_rsa/bridge/binding.dart 26:16 new Binding._internal
package:fast_rsa/bridge/binding.dart 17:45 Binding._singleton
package:fast_rsa/bridge/binding.dart Binding._singleton
package:fast_rsa/bridge/binding.dart 22:12 new Binding
package:fast_rsa/fast_rsa.dart 40:32 RSA.bindingEnabled
package:fast_rsa/fast_rsa.dart RSA.bindingEnabled
package:fast_rsa/fast_rsa.dart 43:9 RSA._call
package:fast_rsa/fast_rsa.dart 79:22 RSA._keyPairResponse
package:fast_rsa/fast_rsa.dart 437:18 RSA.generate
test/rsa_keys.test.dart 32:37 main.<fn>.<fn>.<fn>
package:mockito/src/mock.dart 185:45 Mock.noSuchMethod
test/rsa_keys.test.mocks.dart 75:53 MockRSAKeysProvider.generate
test/rsa_keys.test.dart 36:21 main.<fn>.<fn>

Your stub calls RSA.generate, so it depends on the RSA.generate implementation which involves FFI and loading a dynamically-loaded library on whatever platform you run tests on. Unless you're trying to test package:fastrsa itself, you should avoid calling things such as RSA.generate anyway; that is what you should be replacing with a stub.
The point of a Mock is to test how other code interacts with the mocked object. You cannot use a MockRSAKeysProvider to test the behavior of an RSAKeysProvider itself. If you want to test the behavior of your RSAKeysProvider class, you could change it to accept a stub for RSA.generate:
class RSAKeysProvider {
KeyPair? _keyPair;
KeyPair? get keyPair => _keyPair;
set setKeyPair(KeyPair keyPair) => _keyPair = keyPair;
Future<void> generate(String bits, {Future<KeyPair> Function(int)? generate}) async {
generate ??= RSA.generate;
var keyPair = await generate(int.parse(bits));
_keyPair = keyPair;
notifyListeners();
}
}
and then in your test:
Future<KeyPair> fakeGenerate(int bits) async {
return KeyPair('fakePublicKey', 'fakePrivateKey'); // Or use precomputed values.
}
test('Public and private key should not be null when generated', () async {
final RSAKeysProvider rsaKeys = RSAKeysProvider();
await rsaKeys.generate('2048', generate: fakeGenerate);
expect(rsaKeys.keyPair?.privateKey, isNotNull);
expect(rsaKeys.keyPair?.publicKey, isNotNull);
});

Related

Flutter function returning at await statement

I am using flutter with the cbl package to persist data. Trying to retrieve the entries does not seem to work because the function created is returning at the await statement and not the return statement. This does not seem like the intended result of darts async/await functionality. So I am lost.
task_database.dart
Future<dynamic> getAllTasks() async {
final tasksDb = await Database.openAsync(database); <---------- Returns here
var tasksQuery = const QueryBuilder()
.select(SelectResult.all())
.from(DataSource.database(tasksDb));
final resultSet = await tasksQuery.execute();
late var task;
await for (final result in resultSet.asStream()) {
final map = result.toPlainMap();
final taskDao = TaskDao.fromJson(map);
task = taskDao.task;
// Do something with the task...
print(task);
}
;
return task; <-------------------------------------------- Does not make it here
}
task_cubit.dart
getAllTasks() => {
allTaskMap = TasksAbcDatabase().getAllTasks(),
emit(TaskState(tasks: state. Tasks))
};
What I have tried. I have tried to use Database.openSync instead of Database.openAsync however, the function just returns at the next await statement. I have also tried making getAllTasks asynchronous and awaiting the database as such.
Future<void> getAllTasks() async => {
allTaskMap = await TasksAbcDatabase().getAllTasks(),
emit(TaskState(tasks: state. Tasks))
};
However this has the same issue, when the function from task_database returns prematurely it the returns at the first await function in getAllTasks which is the allTaskMap variable.
Thanks
A function cannot "return prematurely" without a return statement.
The only way the execution is cut short would be an exception being thrown.
I also don't see how you don't get syntax errors, when you don't await the Database.openAsync(database) statement.
So make sure all your awaits are in place. Use the linter to find those that are missing. While you are at it, remove the keyword dynamic from your vocabulary, it will only hurt you if you use it without a need for it. Your return type should be properly typed, then your compiler could tell you, that returning a single task from a function that is clearly supposed to return multiple tasks is not going to work.
Either catch your exceptions and make sure you know there was one, or do not catch them and watch them go all the way through into your debugger.
In addition, following the comment of #jamesdlin, your function definitions are... valid, but probably not doing what you think they are doing.
Future<void> getAllTasks() async => {
allTaskMap = await TasksAbcDatabase().getAllTasks(),
emit(TaskState(tasks: state. Tasks))
};
needs to be
Future<void> getAllTasks() async {
allTaskMap = await TasksAbcDatabase().getAllTasks();
emit(TaskState(tasks: state. Tasks));
}

How to run Amplify's uploadFile function within a dart isolate?

My application has a function that allows to upload videos and other large files to Amazon S3 buckets via Amplify storage. The below function works as expected and uploads a file to my S3 bucket:
Future<String> uploadFile({File? file, String path = "", String fileName = "", String fileExtension = ""}) async {
try {
final key = path+fileName+'.'+fileExtension;
final result = await Amplify.Storage.uploadFile(
local: file!,
key: key,
);
return result.key;
} catch (e) {
throw e;
} }
However, given the size of some of these files, the time required to upload can be large and I want to avoid that my users have to wait until the file is completely uploaded. Therefore I want to run the above function in a dart Isolate, so that the file can continue uploading while my user Navigates to different screens of the app.
I tried to achieve this with the below function:
static uploadFileIsolate(List<dynamic> args) async {
try {
SendPort responsePort = args[0];
File file = args[1];
String path = args[2];
String fileName = args[3];
String fileExtension = args[4];
final key = path+fileName+'.'+fileExtension;
final result = await Amplify.Storage.uploadFile(
local: file,
key: key,
);
Isolate.exit(responsePort, result);
} catch (e) {
throw e;
} }
Which I call like this from my main function:
final p = ReceivePort();
Isolate.spawn(uploadFileIsolate, [p.sendPort, file, path, fileName, fileExtension]);
This is not working and throws the below error:
[VERBOSE-2:dart_isolate.cc(1111)] Unhandled exception:
RangeError (index): Invalid value: Valid value range is empty: 0
#0 DatabaseService.uploadFileIsolate (package:mastory/services/database_service.dart:202:7)
#1 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:300:17)
#2 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)
After reading this issue it makes me think that the error comes from the fact that Amplify is not configured in the 'isolated' environment.
So my question is: How can I make Amplify's uploadFile function work in an Isolate?
Or, if that is not possible, how can I make sure that the uploadFile function continues until completion while allowing my user to Navigate to other screens in my app?

Websocket acting different on different client languages

I have this Django Python Channels setup:
class MarketplaceConsumer(GenericAsyncAPIConsumer):
queryset = TestChannel.objects.all()
serializer_class = TestChannelSerializer
permission_classes = [permissions.AllowAny]
#model_observer(TestChannel)
async def comment_activity(self, message: TestChannelSerializer, observer=None, **kwargs):
await self.send_json(message.data)
#comment_activity.serializer
def comment_activity(self, instance: TestChannel, action, **kwargs) -> TestChannelSerializer:
'''This will return the comment serializer'''
return TestChannelSerializer(instance)
#action()
async def subscribe_to_comment_activity(self, user_pk, **kwargs):
print(user_pk) # PRINTS USER_PK PASSED THROUGH ACTION
await self.comment_activity.subscribe()
When this is run through JavaScript, I do get the expected result, the action prints the user_pk.
However, connecting to the same WebSocket using flutter, the user_pk is not printed, meaning the action specified is not even being called.
Am I implementing this incorrectly with the flutter client?
final channel = IOWebSocketChannel.connect(
Uri.parse('ws://127.0.0.1:8000/ws/marketplace/'));
#override
void initState() {
final data = json.encode({
'action': "subscribe_to_comment_activity",
'request_id': Helpers.generateId(),
'user_pk': 'PCs4o8c17xg',
});
channel.stream.listen((message) {
channel.sink.add(data);
//channel.sink.close(status.goingAway);
print(jsonDecode(message));
});
super.initState();
}
Figured this out! The flutter part somehow needs acknowledgement from the backend server. At least that's what I understand.
Adding an accept method to the backend works this out.
async def accept(self, **kwargs):
await super().accept(** kwargs)
print('connected')
await self.comment_activity.subscribe()

Access flutter tests result in tearDownAll

I need to access the tests result in tearDownAll callback. I want to check if any of the tests failed during the execution.
tearDownAll(() async {
final success = // code here
});
I researched classes like Invoker, Declarer, Engine, LiveTestController and test_core.dart file. Seems like this is the code that does what I need.
var success = await runZoned(() => Invoker.guard(engine.run),
zoneValues: {#test.declarer: _globalDeclarer});
if (success) return null;
print('');
unawaited(Future.error('Dummy exception to set exit code.'));
Unfortunately the engine is not accessible from outside. I've also not found API for checking the tests result in test_core library.
Is there a way to check if any of previous test has failed? I'd like use this inside a tearDownAll() or test() functions of a test group.
I found a possible solution to my question, that requires some extra stuff in the test class.
Basically you have to check every single test result in tearDown and keep track of them, then in tearDownAll you can check if any of the test has failed.
import 'package:test_api/src/backend/invoker.dart';
import 'package:test_api/src/backend/state.dart' as test_api;
final failedTests = [];
tearDown(() {
if (Invoker.current.liveTest.state.result == test_api.Result.error) {
failedTests.add(Invoker.current.liveTest.individualName);
}
});
tearDownAll(() {
if (failedTests.isNotEmpty) {
// do stuff
}
});

How to test async functions that update PublishSubject or BehaviorSubject object (RxDart) in Flutter

I've been learning flutter for a few weeks and come from an Android background so far I love it and I have also been delighted to find that Flutter was designed with testing in mind from day one. However, I've been having an issue running the following test.
main() => {
test('test get popular repos', () async {
final testOwner = Owner(1010, "testLink");
final testRepo =
Repo(101, testOwner, "testRepo", "description", 'htmlUrl', 500);
final testRepoResponse = RepoResponse(List.from([testRepo]), null);
final uiModel = PopRepo(testRepo.owner.avatarUrl, testRepo.name,
testRepo.description, "Stars: ${testRepo.stargazersCount}");
final searchData = SearchData(List.from([uiModel]), null);
final Repository mockRepository = _mockRepository();
when(mockRepository.getPopularReposForOrg("org"))
.thenAnswer((_) => Future.value(testRepoResponse));
final repoSearchBloc = RepoSearchPageBloc(mockRepository);
await repoSearchBloc.getPopularRepos("org");
await expectLater(repoSearchBloc.resultSubject.stream, emits(searchData));
}),
};
class _mockRepository extends Mock implements Repository {}
My RepoSearchBloc takes data from a Repository and transforms it into the Ui model. Finally it posts that now UI-ready data to the Subject
this is the method under test in the RepoSearchBloc
getPopularRepos(String org) async {
if (org == null || org.isEmpty)
return resultSubject.add(SearchData(List(), null));
RepoResponse response = await _repository.getPopularReposForOrg(org);
if (response.error == null) {
List<Repo> repoList = response.results;
repoList.sort((a, b) => a.stargazersCount.compareTo(b.stargazersCount));
var uiRepoList = repoList
.map((repo) => PopRepo(repo.owner.avatarUrl, repo.name,
repo.description, "Stars: ${repo.stargazersCount}"))
.take(3)
.toList();
resultSubject.add(SearchData(uiRepoList, null));
} else {
ErrorState error = ErrorState(response.error);
resultSubject.add(SearchData(List(), error));
}
When I run the test I keep getting this message no matter what I do it seems with either BehaviorSubject or PublishSubject:
ERROR: Expected: should emit an event that <Instance of 'SearchData'>
Actual: <Instance of 'BehaviorSubject<SearchData>'>
Which: emitted * Instance of 'SearchData'
Any ideas how to get this test to pass?
Ended up figuring this out with the help of a user Nico #Rodsevich of the Flutter Glitter community
anyways using his suggestion to use await for
I came up with the following solution which passed
await for (var emittedResult in repoSearchBloc.resultSubject.stream) {
expect(emittedResult.results[0].repoName, testRepo.name);
return;
}
The RxDart library has some subject tests for reference but my subject being posted to asynchronously did not adhere to their test cases so this solution ended up being just what I needed.
Also #Abion47 's comment also seems to do the job when I move async inside the parameter for expected
expectLater( (await repoSearchBloc.resultSubject.stream.first as SearchData).results[0].repoName, testRepo.name);