Today we’ll see how to add unit tests to Xamarin Android application, testing both platform-independent logic and Android-specific features.
Issue with unit tests in Visual Studio 2017
When I started to create my data access methods in MoneyBack, I really wanted to start writing even some basic unit tests of that logic.
My first trial was to add a new project to my solution which contains unit tests. So I checked what kind of projects templates I have available and I I found Unit Test App (Android) project template and added it to my solution. Generated project contained TestsSample class with some unit-looking tests methods, but I had no idea how to execute them. With ReSharper installed, I didn’t have any “Run Tests” option on this project. I read somewhere on the web that this project type is used to execute tests on the device, but I couldn’t figure out how to do it. I gave up.
Then I found another project template – Class Library (Android) with nUnit. Sounds better, nUnit in the name suggests unit tests, so I added this project to my solution. This time when right-clicking the project I had “Run Tests” option, but when clicking on it I got the following exception coming from ReSharper:
“Hmm… Visual Studio 2017”, I thought. “Some bugs for sure”. And I…
… didn’t give up this time and with some help of folks from StackOverflow I figured out how unit tests should be done in Xamarin app. Let’s see that in the next chapters.
Levels of testing Xamarin application
There are basically three “levels” of testing Xamarin apps:
- Classic unit tests of pure .NET/Mono
- standard, good old unit tests of logic independent from targeting platform (Xamarin.Android/iOS/Windows Phone)
- unit-testing frameworks can be used (nUnit / xUnit)
- Platform-specific tests
- unit tests of functionalities specific to targeting platform (e.g. Bluetooth, Location, GPS, SMS etc.)
- specific for each platform
- don’t contain GUI tests
- executed on the emulator/device
- UI tests
- tests of UI elements of the app and how those react for input (touch) events
- executed by cloud/testing lab services (local or remote)
We’ll dive into the first two, UI tests is more complex topic, which requires setting up tests lab or using cloud testing services. For small projects, in the beginning it’s not necessary.
Classic platform-independent unit tests
In order to simply use ReSharper for executing unit tests of logic, which is independent from Android/iOS/Windows Phone, there is a need to add a new project using template Unit Test Project (.NET Framework) to our Xamarin solution.
To such project, you can add your favourite unit-testing framework (such as nUnit or xUnit) by simply installing it from Nuget. After, ReSharper allows to run your unit tests as in old good times:
The exception I was getting with ReSharper previously was because I added the project using template Class Library (Android) which has its target set to Android device/emulator. ReSharper doesn’t know about any Android-specific (or Xamarin-specific) testing, so it was displaying unhandled error.
Platform-specific unit tests
In order to test platform-specific (in out case Android-specific) functionalities of your application, the tests need targeting runtime environment to run. It means they need to be executed on the physical device or emulator. Providers of the most popular unit-testing frameworks created wrappers for on-device testing. I’m using NUnit Xamarin Runners.
First of all, a new project should be added to our solution. To make things easier, download NUnit Templates for Visual Studio (although it officially supports VS up to 2015, it also works in VS 2017) and install it. After, add new project using appropriate template – I chose NUnit 3 Test Project (Android). What’s interesting, the newly created project contains MainActivity class with the following content:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity | |
{ | |
protected override void OnCreate(Bundle savedInstanceState) | |
{ | |
base.OnCreate(savedInstanceState); | |
global::Xamarin.Forms.Forms.Init(this, savedInstanceState); | |
// This will load all tests within the current project | |
var nunit = new NUnit.Runner.App(); | |
// If you want to add tests in another assembly | |
//nunit.AddTestAssembly(typeof(MyTests).Assembly); | |
// Do you want to automatically run tests when the app starts? | |
nunit.AutoRun = true; | |
LoadApplication(nunit); | |
} | |
} |
As you can see, this is an Activity, so it will be launched as a separate app with its own GUI. It’s built using Xamarin.Forms. The comments in auto-generated class are obvious, so I won’t explain the details here. That’s just what nUnit wrapper for Android on-device tests does – it allows to be run as a separate, small application with a GUI allowing to run unit tests directly on the device (similarly to ReSharper tests runner we use directly in Visual Studio).
One testable piece of MoneyBack application is the Repository used for database operations (by the way I refactored and made it generic, you can see how it looks currently on GitHub), so I added RepositoryTests class to MoneyBack.Android.Tests project with two test methods:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Test] | |
public void one_new_person_inserted_adds_one_new_row() | |
{ | |
// given | |
var person = new Person() | |
{ | |
Name = "A", | |
LastName = "B" | |
}; | |
var repo = new Repository<Person>(InMemorySqliteConnection); | |
// when | |
var numRows = repo.Insert(person).Result; | |
// then | |
Assert.AreEqual(1, numRows); | |
} | |
[Test] | |
public void new_person_added_has_id_primarykey_generated() | |
{ | |
// given | |
var person1 = new Person | |
{ | |
Name = "A", | |
LastName = "B" | |
}; | |
var person2 = new Person | |
{ | |
Name = "A", | |
LastName = "B" | |
}; | |
var repo = new Repository<Person>(InMemorySqliteConnection); | |
// when | |
var n1 = repo.Insert(person1).Result; // getting Result in order to force Task's completion before continuing | |
var n2 = repo.Insert(person2).Result; | |
// then | |
Assert.Greater(person1.Id, 0); | |
Assert.AreEqual(person2.Id, person1.Id + 1); | |
} |
The interesting part is that as I’m using SQLite database in my app, I wanted to test it without creating database file on the device. For that purpose, when creating SQLiteAsyncConnection in order to initialize my Repository, I passed to its constructor a “:memory:” string, which makes SQLite database created in-memory (there is no physical file created):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public SQLiteAsyncConnection InMemorySqliteConnection; | |
[OneTimeSetUp] | |
public void Init() | |
{ | |
InMemorySqliteConnection = new SQLiteAsyncConnection(":memory:"); | |
} |
Such database lives in device’s memory only for the time of our unit tests.
After having tests written, there’s a time to execute them. As the project contains MainActivity class, we simply deploy and run it on a device/emulator, which presents the following view:
If any of our tests is not passed, we can see the failure’s details:
We can of course debug our units tests in Visual Studio. The GUI on the device also allows to re-run the tests and see results of all of them.
Summary
We’ve seen how to add two new projects to Xamarin solution: one containing platform-independent unit tests (old good ones), the other one testing Android-specific stuff which is run directly on the device or emulator.
For bigger and more “real” apps, UI testing would also be necessary. This however requires using some cloud testing lab or setting up your own testing environment. More details can be found here.
If you have any experience in testing Xamarin applications, share your insights/advices 🙂