I have been working on Prism for Xamarin.Forms for a long time now, and one thing kept causing me nothing but headaches… testing anything that involves the Xamarin.Forms Application class. I had a work-around in place for awhile now, but it started causing me issues trying to automate my CI process. The work-around involved creating a new Test build configuration with a number of #ifdefs. While this worked, nothing but pain came during my CI build scripts. So, I had to figure this out once and for all.
Running tests against the Xamarin.Forms Application class is a well known pain in the butt. Let’s assume we have a simple test like this (I’m using xunit):
You would think that this seems like a pretty basic test that should pass with no problems. Well, you would be wrong! This fails immediately with the well known “System.InvalidOperationException : You MUST call Xamarin.Forms.Init(); prior to using it.” message.
The reason this exception occurs is because Xamarin.Forms is trying to initialize the platform code required to run the Application. This is equivalent to:
- Android (in MainActivity.cs): Xamarin.Forms.Forms.Init(this, bundle);
- iOS in (AppDelegate.cs): Xamarin.Forms.Forms.Init();
Obviously we are not in one of those platforms, but rather a simple test library. So, how do we fix this so that we can actually test our Xamarin.Forms code?
In order to solve this problem, I had to dig into the Xamarin.Forms source code. The first thing I did was search for the error message “You MUST call Xamarin.Forms.Init()”. The search results found two instances of this message both in the Device.cs class. One in the setter of the Info property and the other in the setter of the PlatformServices property.
If you follow that up the chain, you’ll see that for each platform in the Forms.Init() call, these values are being set based on the platform. So this means we need to create our own Init() call for our unit tests!
Note: My test project is a .NET Core class library using XUnit
In our Init() method, we need to make sure we set the DeviceInfo.Info and Device.PlatformServices property to an appropriate mock object. Let’s go ahead and create all of our supporting mock objects:
Next, lets create our own static Init() method and make sure we properly instantiate our objects. We can’t forget to register our mocks with the DependencyService.
Now, let’s update our test class to add a ctor and make a call to our custom Init() method.
Let’s re-run our tests, and BAM! SUCCESS!
Now that this is properly mocks we can start testing all kinds of cool stuff. We can test converters, navigation, resources, markup extensions, and all kinds of other interesting stuff.
Be sure to check out all the source code on GitHub. As always, feel free contact me on my blog, connect with me on Twitter (@brianlagunas), or leave a comment below for any questions or comments you may have.