realm-dart
realm-dart copied to clipboard
unit tests using the realm
Hello everyone, I hope you are all well!
I would like to know if there is any way to integrate the realm with the unit testing features with mockito/mocktail. I'm trying to test the realm mapping class, but I'm not having success.
Is there any way to mock the return results?
Thanks!
I find it easier to open an actual in-memory realm for unit tests. Would that work for you?
Good morning @nielsenko!
Thank you very much for answering.
Sorry for taking so long to respond.
One question (sorry for my ignorance, I'm new to Flutter), in case, wouldn't this qualify as an integrated test?
As I only need a stub to know the return, would I need the realm in memory so I can test?
Hi Fernando
Since you explicitly mentioned mockito
let me quote the author on best practices:
Testing with real objects is preferred over testing with mocks - if you can construct a real instance for your tests, you should!
I totally agree. Don't stub unless you have to. Since you can easily spin up a realm in-memory, I suggest you do that for your tests.
Now "all" you need to stub/fake is the backend synchronisation, if you want to avoid the burden of doing an actual integration test with the backend.
One approach is to have a fake that implements Realm
simply delegating to an actual in-memory realm, but faking Realm.subscriptions
, Realm.syncSession
, SubscriptionSet.waitForDownload
, SubscriptionSet.waitForUpload
and Session.getProgressStream
to the extend you need it. For many test-cases that may not even be necessary.
For the test of the SDK itself we actually spin-up a fresh backend and do proper integration tests whenever we test sync related functionality. It is not too hard to automate setup and teardown using realm-cli. That is one of the joys of SaaS 😄
Thanks for replying again, @nielsenko.
We are currently using mocktail for testing.
As mockito currently needs generated files, we chose to use mocktail even to not have this dependency.
Sorry to bother you, but I was wondering if you have any issues with the Realm framework and how it should be used (in this case, best practices for collection names and relationships)?
You are welcome.
I think it would work about the same with mocktail
.
Regarding:
.. I was wondering if you have any issues with the Realm framework and how it should be used (in this case, best practices for collection names and relationships)?
I'm not sure I if I understand your question correctly, but there are known outstanding issues with the SDK. First of all we are still in beta, and there are stuff missing, such as embedded objects and some datatypes - map, set, and mixed comes to mind.
We also have at least one significant bug reported regarding crash during hot-restart #728
With regards to best practices, there are documentation available here https://www.mongodb.com/developer/products/realm/
I hope this helps
About your question regarding relationships, I have a structure like this in my collection:
{
"_id": "object-id",
"name": "name",
"created_at": {
"user_id": "object-id",
"username": "name"
},
"updated_at": {
"user_id": "object-id",
"username": "username"
}
}
And about the structure, would it be correct to have relationships where the collection has its name separated by an underscore?
I created 2 private classes (within my target class) defined as _created_at and _updated_at (following the template in the doc) and when generating the g.dart file, the attribute is transformed like this in the get and set methods:
@override
createdat? get createdAt =>
RealmObject.get<createdat>(this, 'created_at') as createdat?;
@override
set createdAt(covariant createdat? value) =>
RealmObject.set(this, 'created_at', value);
@override
updatedat? get createdAt =>
RealmObject.get<updatedat>(this, 'created_at') as createdat?;
@override
set createdAt(covariant updatedat? value) =>
RealmObject.set(this, 'created_at', value);
And when running the project, it is informed that the above attributes were not found
The main class looks like this:
// main collection
@RealmModel()
@JsonSerializable()
@_ObjectIdConverter()
class _collection {
@PrimaryKey()
@MapTo('_id')
late ObjectId id;
@MapTo('name')
late String name;
@MapTo('created_at')
late _created_at? createdAt;
@MapTo('updated_at')
late _updated_at? updatedAt;
}
// relationship classes
@RealmModel()
@JsonSerializable()
@_ObjectIdConverter()
class _created_at {
@MapTo('id')
late ObjectId userId;
@MapTo('name')
late String userName;
}
@RealmModel()
@JsonSerializable()
@_ObjectIdConverter()
class _updated_at {
@MapTo('id')
late ObjectId userId;
@MapTo('name')
late String userName;
}
Please forgive me if I'm being annoying, it's just that I would like to spend the possibilities of use without having to write anything native to be able to support (but if there is a need, I'll have to).
Thank you very much for your attention.
Hi Fernando
Unfortunately the above schema would require embedded objects, which we don't support yet. Also, adding json annotations at the model level will not do what you probably expect (see #683).
In general, whenever you use embedded objects (which are very common in MongoDB) you need to hoist out the object into a separate collection and make a link. That is until we support embedded objects 😒
I'm not sure what the @_ObjectIdConverter
does in the above code, but if we forget about that and @JsonSerializable()
then the following model might work for you (assuming you can change the schema on Atlas):
// main collection
import 'package:realm_dart/realm.dart';
part 'issue870.g.dart';
@RealmModel()
@MapTo('collection') // you need a collection in Atlas to match
class _Collection {
@PrimaryKey()
@MapTo('_id')
late ObjectId id;
// @MapTo('name') // <-- not needed, since field has the same name
late String name;
@MapTo('created_at')
late _Change? createdAt;
@MapTo('updated_at')
late _Change? updateAt;
}
@RealmModel()
@MapTo('change') // you need a collection in Atlas to match
class _Change {
@PrimaryKey()
@MapTo('_id') // As this is hoisted into its own collection, it needs a _id field of its own
late ObjectId id;
@MapTo('user_id')
late ObjectId userId;
@MapTo('username')
late String userName; // why not username both places? (or user_name on Atlas?)
late DateTime at; // I assume you forgot the actual timestamp in your model
}
Notice I have introduced a new collection called change
(on Atlas) and Change
(in Dart) that is used for both the createdAt
and updatedAt
fields.
Is this a feasible way forward for you?
BTW, unless you are already bound to a schema on Atlas, I suggest enabling developer mode under sync, which will allow the client to do additive changes to the schema.
Eventually you will do a destructive change, in which case the easier path forward is just to delete the old Atlas App Service application and start again.
I find that to be the fastest way to experiment with the schema - if you do greenfield development.
Also, getting familiar with realm_cli
is time well spent. It will allow you to script setup and teardown of your apps on Atlas.
Thank you very much for replying.
You gave a great help. I talked to my techlead and we'll set up a meeting with the backend team so we can rethink the structure.
As there is still no support for Map types, there would be a need to change to the model you presented me.
Again, thank you so much for your help and God bless you.
You are welcome. I don't know about your timelines, but we are actively adding missing features. Unfortunately I cannot commit to any particular date, but embedded objects are high on our list of priorities.
@nielsenko Is there any example available for unit testing either with Mockito or without?
I've created a singleton class inside database_service.dart
class DatabaseService {
static final DatabaseService _instance = DatabaseService._internal();
factory DatabaseService() {
_instance._openRealm();
return _instance;
}
final Logger log = Logger('DatabaseService');
DatabaseService._internal();
late Realm _realm;
Realm get realm => _realm;
final Configuration _config =
Configuration.local([Patient.schema]);
_openRealm() {
_realm = Realm(_config);
}
closeRealm() {
if (!realm.isClosed) {
realm.close();
}
}
}
from the tests if I try to call DatabaseService()
, I'm getting this error
/test/data/repository/patient_repository_test.dart": Invalid argument(s): Failed to load dynamic library '/usr/local/Caskroom/flutter/3.0.4/flutter/bin/cache/artifacts/engine/darwin-x64/../Frameworks/realm.framework/Resources/librealm_dart.dylib': dlopen(/usr/local/Caskroom/flutter/3.0.4/flutter/bin/cache/artifacts/engine/darwin-x64/../Frameworks/realm.framework/Resources/librealm_dart.dylib, 0x0001): tried: '/usr/local/Caskroom/flutter/3.0.4/flutter/bin/cache/artifacts/engine/darwin-x64/../Frameworks/realm.framework/Resources/librealm_dart.dylib' (no such file)
@tushkaty I can reproduce your issue wrt. loading librealm_dart.dylib
when invoking flutter test
to run widget tests etc. I will get back to you, when I know more.
Just to add that I'm facing the same problem reported by @tushkaty when trying to uniting test my application. But one thing is different: I'm not even calling/using Realm in my test file.
I'm getting this:
Failed to load "[path]/X_controller_test.dart": Invalid argument(s): Failed to load dynamic library '/Users/germano/fvm/versions/stable/bin/cache/artifacts/engine/darwin-x64/../Frameworks/realm.framework/Resources/librealm_dart.dylib': dlopen(/Users/germano/fvm/versions/stable/bin/cache/artifacts/engine/darwin-x64/../Frameworks/realm.framework/Resources/librealm_dart.dylib, 0x0001): tried: '/Users/germano/fvm/versions/stable/bin/cache/artifacts/engine/darwin-x64/../Frameworks/realm.framework/Resources/librealm_dart.dylib' (no such file)
@wilsleygermano It's because some of your test cases using the Realm class object.
@tushkaty you are correct. I thought running a specific test file, that doesn't use realm, would avoid that error. Anyway, now you remind me that I'm facing problems with my realm tests the same way you have (using Mockito).
Hey @tushkaty and @wilsleygermano, It is a known issue #791. We have it in the backlog and hope we will find some solution soon. The workaround is to copy the library inside the execution folder of flutter_tester.
Hi @tushkaty and @wilsleygermano,
The new release is available here realm 0.10.0+rc.
You can upgrade the realm to 0.10.0+rc
and then to run the new available command flutter pub run realm install
from the project root folder. This will download the library and you will be able to run your tests.
I hope this will help.