Today I Learned How To Create Xamarin iOS and Android Unit Tests

I’m currently working on a notification plugin for Xamarin Forms and wanted to setup some unit tests.  The problem is my code accesses the device-specific notification systems in iOS and Android.  This means I can’t just run my unit tests on Windows.  Instead I need to run the unit tests in a iOS or Android environment.  In my case this means an emulator.

How to do that?  Use the NUnit 3 Xamarin Runners.  It was not clear how to correctly create the test projects but the way that worked for me was this.

Create a Shared Project For the Tests

What is a shared project?  I don’t remember the exact problem I had but I first tried creating a portable project.  That failed for some reason I can’t remember so I switched to a shared library.

Create Shared Project

This will be the project your tests will reside in once you write them.  For example in the XPlugins Notifications project I have the following tests.

Shared Project with Tests

You won’t have any tests but should create one now just for testing.  Create a test that simply passes or fails.  Something like the below.

[TestFixture]
class ExampleTests
{
    [Test]
    public void SmokeTest()
    {
        Assert.That(true);
    }
}

Since this is a shared project we can’t actually run it.  The shared project needs to be included in an Android or iOS project.

Create Droid Test Project

First the Droid project.  It’s just a standard Android application.

Create Droid Test Project

In the new Android project add a reference to your test’s project.

Reference Shared Test Project From Droid Project

Then you need to add the following code to the MainActivity classe’s OnCreate method:

// This will load all tests within the current project
// and run them.
 var nunit = new NUnit.Runner.App {AutoRun = true};

The full OnCreate method will look something like this:

protected override void OnCreate(Bundle savedInstanceState)
{
    base.OnCreate(savedInstanceState);

    Xamarin.Forms.Forms.Init(this, savedInstanceState);

    // This will load all tests within the current project
    var nunit = new NUnit.Runner.App {AutoRun = true};

    LoadApplication(nunit);
 }

Now you should be able to run your test.  Set the droid project as your startup project and run it either on your device or your favourite emulator.  You should get the below output if everything is working.

Droid Overall Test Results

Droid Test Results

Create iOS Test Project

Creating the iOS test project is similar to the Android.  First add a basic iOS project.  In our case we add a universal iOS project.

Create iOS Test Project

Add a reference to your test project.

Reference Shared Test Project From iOS Project

Then add the code snippet to run the tests to the FinishedLaunching method in the AppDelegate class.

// This will load all tests within the current project
var nunit = new NUnit.Runner.App {AutoRun = true};

The full FinishedLaunching method will look something like this:

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    Forms.Init();

    // This will load all tests within the current project
    var nunit = new NUnit.Runner.App {AutoRun = true};

    LoadApplication(nunit);

    return base.FinishedLaunching(app, options);
 }

Now the tests should run on an iOS device.

iOS Overall Test Results

iOSTest Results

Writing a Test for a Specific Platform

Your tests might require a reference to a specific platform.  If that is the case then you can use a compiler directive.

#if __ANDROID__
    _schedulerToTest = new Notifications.Droid.NotificationScheduler();
#elif __IOS__
    _schedulerToTest = new Notifications.iOS.NotificationScheduler();
#else
    throw new Exception("Invalid envrionment.")
#endif

An example of a test file looks like this:

/// <summary>
/// Tests to make sure notifications can be found.
/// </summary>
[TestFixture]
internal class FindTests
{
    /// <summary>
    /// The schedule to test.
    /// </summary>
    private INotificationScheduler _schedulerToTest;

    /// <summary>
    /// Load the correct scheduler based on the environment we are in.
    /// </summary>
    [OneTimeSetUp]
    public void OneTimeSetUp()
    {
#if __ANDROID__
        _schedulerToTest = new Notifications.Droid.NotificationScheduler();
#elif __IOS__
        _schedulerToTest = new Notifications.iOS.NotificationScheduler();
#else
        throw new Exception("Invalid envrionment.")
#endif
 }
    /// <summary>
    /// Should find a created notification.
    /// </summary>
    [Test]
    public void NotificationExists()
    {
        // Create the notifiaction to find.
        const string expectedNotificationTitle = "Test Notification";
        const string expectedNotificationMessage = "This is a test notification.";

        var expectedNotificationId = _schedulerToTest.Create(expectedNotificationTitle, expectedNotificationMessage, DateTime.Now.AddHours(1));

        // Try to find it.
        var resultNotification = _schedulerToTest.Find(expectedNotificationId);

        Assert.That(resultNotification, Is.Not.Null);
        Assert.That(resultNotification.Title, Is.EqualTo(expectedNotificationTitle));
        Assert.That(resultNotification.Message, Is.EqualTo(expectedNotificationMessage));
    }
 }

Remember these tests require items specific to the iOS or Android environment.  In this case notifications.  If you are just testing business logic that does not require a feature on a specific device then just add a standard Class Library project to hold your tests.

Save

Save

This entry was posted in Goal App, Today I Learned and tagged , , , , . Bookmark the permalink.