The goal is to be able to unit test my PCLs without deploying to the device for faster test time.
In 0.78.1 we were able to new up a class overriding RealmObject, however we are unable to do so in 0.81.0.
This blocks us from upgrading the version since we are entirely unable to test our entire data access layer.
I should be able to:
In 0.78.1 I am able to new up a RealmModel, set properties to it and use it in my mocks, while mocking a wrapper layer around the Realm object as a workaround on #537.
In 0.81.0 when setting a property in the unit tests the object checks if it is managed, throwing an exception because of an unimplemented method in the "bait" nuget.
Write a nunit project in Xamarin studio that uses the bait Realm nuget package to test the business logic of your app.
New an object that extends RealmObject and set a property to it.
Using 0.78.1 the code will work, using 0.81.0 it fails.
// Model
public class Person : RealmObject
{
public string Name { get; set; }
}
// Failing unit test
[Test]
public void Test_PersonCreation()
{
new Person { Name = "This fails on 0.81.0, but not on 0.78.1" }
}
Realm version(s): 0.78.1 works, 0.81.0 fails
Xamarin Studio version: 6.1.2
Which operating system version and device: Nunit projects running on an iMac and Windows 10 against the bait nuget package.
@joseprl89 can you please provide more details of the error you are getting and exactly how you created your solution.
I tried creating a plain SingleView project using PCL to contain Realm and the model definition and then added an NUnit project. When I tried running tests, the exception I got was:
_System.PlatformNotSupportedException : The PCL build of Realm is being linked which probably means you need to use NuGet or otherwise link a platform-specific Realm.dll to your main application._
@AndyDentFree yes, that is the exception.
The gist is, I want to be able to test my code which depends on Realm without importing the realm nuget, using the bait nuget one instead.
@joseprl89 I believe the exception is caused by calls to RealmObject.RaisePropertyChanged. Can you show us a sample test case so we can have more context into what you want to test and how to best handle that scenario?
Thanks.
@nirinchev we are currently following an onion architecture:
http://jeffreypalermo.com/blog/the-onion-architecture-part-1/
With that, Realm is in a PCL in the infrastructure layer, which implements data access interfaces and maps the Realm objects into Realm models in order to return objects uncoupled to the underlying platform.
We want to test the Realm PCL to ensure the queries and mappings are correct, but we are hindered because we can't new a model and set a property on them in 0.81.0, and we can't mock the Realm object and need to wrap it in an interface.
I had to create a "mock" Realm assembly for 0.81.0 to do "non-device"-based unit testing that included "RealmObject" subclasses.
Basically the 5 public RealmObject props have their "RealmPCLHelpers.ThrowProxyShouldNeverBeUsed();" removed and I implemented the public Equals method...thus a RealmObject becomes a specialized POCO, all the custom getters/setters and custom public methods can tested along with reading/verifying the Realm attributes, etc...
So in unit-testing where Model subclasses RealmObject, something as simple as this is valid to perform in PCL test code:
[Test()]
public void TestCase()
{
var ro = new Model();
ro.Key = "key";
}
@sushihangover I was thinking of adding a setting for _savvy users_ such as yourself so RealmPCLHelpers.ThrowProxyShouldNeverBeUsed can be turned off. So, by default, it is on and catches the people who forget to add the nuget to their device-specific projects.
Two possible designs:
Realm property.Realm which is passed the calling method name. That way, you can easily turn it of for all cases but could also have much more discriminating logic so it was only disabled for a few specific test methods.@AndyDentFree Either 1 or 2 would work...but it just seems to have a code smell about it.... and in the end, I believe as Realm/C# usage increases you will see this unit test issue increase exponentially. A lot of projects add test helper packages/Nugets and "just" adding a Realm.Testing package to x|NUnit project would be my ideal world for myself and clients.
Personally I would like RealmObject to turn into an interface (IRealmModel) and thus I could substitute an interface-based implementation in the unit tests.
Along that line, I really believe that the non-subclassing of the RO object model is soooooo limiting in design. I understand it from simplicity standpoint of weaving, serialization, etc..., but I really need to chain model inheritance and then apply the "RealmObject" to the sealed/final class for storage in a Realm (I'm not looking for Realm to blow out this sealed object into multiple classes and save all of those subclasses as separate objects, I am looking for just one flatten object in the Realm file).
โโโ Person
โโโ Talent
โย ย โโโ OnScreen
โย ย โย ย โโโ Actor (`Sealed` and tagged w/ `IRealmObject`)
~~~
โย ย โโโ OffScreen
โย ย โย ย โโโ VoiceActor (`Sealed` and tagged w/ `IRealmObject`)
~~~
โโโ Crew
~~~
This example is from a real life project that will end up using SQLite/POCOs w/ Entity Framework as the only way that I can manage creating and maintaining a hundred+ RealmObject classes that can only be one level deep is to code generate the RealmObject classes via T4 templates, that themselves are generated from their existing model.
Re-designing their existing data model just for mobile, which should be done IMHO, is out as their existing ETL logic for syncing is poured in concrete and out of my hands. The client bulked at the T4 design, they have never used code generation templates before and thus are not comfortable with it plus they already have a full C# inheritance-based POCO model with custom setters/getters and public methods (yes, their models have intelligence ๐ฉ ๐ซ) that I could just borrow if I could say, seal those final classes and apply an IRealmModel interface ;-)
It almost Christmas so I'll add that to my list for Santa ๐ , or maybe some rich benefactor will task me to write :man_dancing:
As I fear this discussion might get too big and distract us from the original issue, I extracted the gist of it in #1065. @joseprl89 we'll try to get this sorted and if it's urgent for you (e.g. there are features in 0.81.0 that you need, but can't use due to this issue), we can provide you with a nightly build once we merge the fix.
Regarding inheritance, it is high on our backlog, but there are a lot of moving parts required to support it, so we're still evaluating how to provide a lot of value (e.g. being able to write queries) without the complexity exploding ๐
Still, I am fairly confident that the first iteration will still have RealmObject in the root.
Finally, in terms of testing, there are multiple approaches and we haven't spent enough time evaluating them to confidently say which one is the best. We could go with an interface, or a mockable set of classes similar to this one that you'll be able to use as a drop-in replacement for the real ones. The more feedback we get from you folks, the better informed decision we'll be able to make when we get to cracking that problem, so keep it coming ๐
@sushihangover have you documented anywhere how to replace the PCL with a custom implementation of it?
Also, regarding #1065, not crashing may not satisfy all use cases we have. At the moment, using 0.78.1 I just realised it fails as well when using a .Equals on a RealmModel.
@joseprl89 No, I have not publicly documented this anywhere as I ended up creating a private/custom Realm.Testing Nuget to provide various Realm class mocks/substitutions for unit testing.
Personally I would take @nirinchev up on his offer to provide a nightly build w/ the included fix to see if their purposed solution works for your unit-testing use-case (Note: I have not looked to see if this purposed fix has been merged into master as I went a different direction using that custom Realm.Testing Nuget...)
@sushihangover Do you have a version of that Realm.Testing Nuget publicly available anywhere, or maybe want to push a PR up for it? This seems like a problem that many people are going to run into (selfishly including myself).
@nirinchev How are you all going about testing PCL code right now? We rolled back to 0.80.0 to get unit tests working, but as @joseprl89 mentioned .Equals on a RealmModel throws the unimplemented exception. Perhaps the nightly build you mentioned addresses this? Not being to unit test with these implementations is incredibly limiting.
@benfrye we workedaround the equals crash by overriding it, but that feels like it is boilerplate i dont really want to maintain.
@joseprl89 Hmm where are you overriding? Just in your testing project, in a class you inherit from that overrides RealmObject, or in each implementation of your RealmObjects? None of those solutions sound tenable to me...
in each of the Realm models. Yeah, it's definitely not ideal or a nice approach, but it's either this, or no unit tests in our data access layer (which is quite an important layer to have properly tested IMO)
@benfrye Missed your question : https://github.com/realm/realm-dotnet/issues/1059#issuecomment-274187837
No I do not have any publicly available right now. Internally I maintain a custom build of Realm that produces a matching Realm.Testing package thus everything is in "sync". I would need to figure out how to avoid the type forwarding issues with Xamarin.iOS to publish it as each Realm.Testing version (assembly) is "hard coded" to a Realm version (assembly) due to use of extensions (the Xamarin linker would fail due to type forwarding that C# extensions create naturally but causes the Linker to fail which presents a huge problem in producing iOS build). I either have to convert the extensions to actual class methods ๐ข or .... ?
Hi everyone, sorry to be the bearer of bad news, but we have had other priorities and haven't really looked into designing a comprehensive testing solution. That being said, I hacked a very rough version that might help you solve your immediate problems. It's very untested, but here's the gist of it: I've introduced a new assembly-level attribute,[RealmTestingSetup] , you can apply (preferably in an #if TESTING) that optionally disables weaving and/or throwing exceptions in the PCL:
ThrowPCLExceptions controls whether the public methods in the PCL assembly will throw exceptions. If you opt out of it, by setting it to false, you can call them, but they will return default values (in most cases).ExecuteWeaver controls whether the Fody weaver will run. If you disable weaving, RealmObject inheritors will be left untouched, so will behave mostly like a POCO. Keep in mind that disabling weaving will also turn off some functionality that you get out of the box, such as PropertyChanged notifications, and automatic initialization of IList/IQueryable properties.I know it's very rough and probably not exactly what you folks were hoping for, but I hope it might help you get unblocked and allow you to test some basic scenarios. Here's the PR that introduces it (#1201), so if you have any comments or suggestions how to make it more useful, please chime in.
Once again, sorry we haven't been able to focus more energy into making Realm more testable and hope to look into that once we land some of the major features we're cooking.
Finally, here's the unofficial NuGet - you'll need to remove the previous version of Realm. Keep in mind that this is prerelease software, hasn't gone through rigorous testing, use at your own risk, yada, yada :)
Thanks @nirinchev, I'll give this a shot. Appreciate the quick turnaround on providing a workaround.
Edit: FWIW, after looking into this this morning, the limitations of turning off the ExecuteWeaver is fairly limiting. Not being able to test RealmList generation is fairly detrimental to test coverage. I realize this is probably because at 0.81.0 you all moved to system specific implementations? Until a more tenable testing solution is available looks like we're going to be stuck on 0.80.0 :(
Hi @benfrye sorry I missed your comment because of the edit. Re turning off the weaver - yes, I assumed that would be the case which is why I made it optional. As for RealmList generation - can you share a sample unit test that you were able to run against 0.80.0 but are unable to now?
For instance using Newtonsoft.Json to deserialize a JSON representation of my model with a list of other RealmModel instances. The instances do not get generated and the list is null. This makes it impossible to test deserialization of JSON into expected model structure properly.
That's interesting - as far as I can remember, the PCL in 0.80.0 should not create a List. I assume it's Newtonsoft.Json that would create a RealmList and set it. As far as I can tell, with the new binaries I uploaded, even in PCL, we should create a list and Newtonsoft.Json should use the Add method to populate it. Can you elaborate what exception you're getting when deserializing?
No exception during deserializing, simply when you go to test the model that comes out the other end, the List is not populated and is in fact null. So it fails when you try to compare expected to actual.
Does that happen with ExecuteWeaver = false?
Yes.
Did you try running it with ExecuteWeaver = true, ThrowPCLExceptions = false? The weaver setting was designed for some more specific use-cases and perhaps I shouldn't have mentioned it.
Yes, we get exceptions about properties not being able to be set to null, even if those properties aren't null in the JSON representation
Newtonsoft.Json.JsonSerializationException : Error setting value to 'Id' on 'Article'.
----> System.ArgumentNullException : Value cannot be null.
Parameter name: element
Has there been any more thought put into proper unit testing strategies around these limitations?
Unfortunately we've had to focus on higher priority features, so nothing to report on that front yet.
Updating here as well as in #1481 but it appears the bait and switch testing limitations mentioned previously have been resolved in a later version. I have verified it working in at least 1.5.0 and 1.6.0.
Most helpful comment
As I fear this discussion might get too big and distract us from the original issue, I extracted the gist of it in #1065. @joseprl89 we'll try to get this sorted and if it's urgent for you (e.g. there are features in 0.81.0 that you need, but can't use due to this issue), we can provide you with a nightly build once we merge the fix.
Regarding inheritance, it is high on our backlog, but there are a lot of moving parts required to support it, so we're still evaluating how to provide a lot of value (e.g. being able to write queries) without the complexity exploding ๐ Still, I am fairly confident that the first iteration will still have
RealmObjectin the root.Finally, in terms of testing, there are multiple approaches and we haven't spent enough time evaluating them to confidently say which one is the best. We could go with an interface, or a mockable set of classes similar to this one that you'll be able to use as a drop-in replacement for the real ones. The more feedback we get from you folks, the better informed decision we'll be able to make when we get to cracking that problem, so keep it coming ๐