Creating Xamarin.UITests for Xamarin.iOS Creating the Test Fixture

Creating Xamarin.UITests for Xamarin.iOS
Now that the solution is prepared, we can start writing the UITests for this application, starting with the iOS application. It
is only possible to run test iOS applications on OS X with Xamarin Studio, so there is no corresponding Visual Studio
examples or screenshots for this section. You can still write and compile the tests in Visual Studio.
This application has a fairly simple workflow, so we will be able to keep all of the UITests in a single test fixture. This one
test fixture will have four tests in it:
A 15-digit Credit Card Number – The application should display an error message that the credit card number is
too short.
A 17-digit Credit Card Number – The application should display an error message that the credit card number is
too long.
No Credit Card Number – The application should display an error message that the value isn't a credit card
number.
A 16-digit Credit Card Number – The application should display a new screen with a message that the credit card
number is valid.
In addition to the test methods, the test fixture will also have code for the following infrastructure:
Determine the path to the AppBundle – The path for the application will not change, so we will initialize an
instance variable in the test fixture setup method.
Initialize IApp – A test should have a new instance of IApp each time it is run. This will minimize the chance of
one test contaminating or interfering with another test. We will initialize IApp in the setup method that is called
before each test is executed.
This section assumes that you are familiar with writing tests and NUnit. If you are not, then you should go and read the
NUnit documentation.
Creating the Test Fixture Class
Lets get started with adding the test fixture to the UITest project. Add a new class called ValidateCreditCardTests to
the test project and adorn it with the TestFixture attribute. Then edit the class so that it resembles the following code
snippet:
using NUnit.Framework;
using Xamarin.UITest;
using System.Reflection;
using System.IO;
using Xamarin.UITest.Queries;
using System.Linq;
using CreditCardValidation.UITests;
# needed for refactoring, below
[TestFixture]
public class ValidateCreditCardTests
{
IApp _app;
public string PathToIPA { get; private set; }
[TestFixtureSetUp]
public void TestFixtureSetup()
{
PathToIPA =
"../../../CreditCardValidation.iOS/bin/iPhoneSimulator/Debug/CreditCardValidationiOS.app";
}
}
The TestFixtureSetup method is run once, the first time that NUnit instantiates the TestFixture class, and will set the
path to the app bundle.
Writing the First Test
The first test will prove that we display the correct error message when a short credit card message is entered. Once
again, edit the test fixture and add the following test and method:
[Test]
public void CreditCardNumber_TooShort_DisplayErrorMessage()
{
/* Arrange - set up our queries for the views */
ConfigureTest();
_app.Repl();
/* Act - enter the credit card number and tap the button */
/* Assert - make sure that the error message is displayed. */
}
void ConfigureTest()
{
_app = ConfigureApp.iOS
.AppBundle(PathToIPA)
.StartApp();
}
ConfigureTest will initialize the IApp instance variable. It may seem a bit excessive at this point, but we will see how
important it is in the next section on refactoring. Once _app hhas been initialized, the test will invoke the REPL which we
will use to explore the user interface and create the queries for this test.
Before we run this test it is important to rebuild the iOS application to ensure that there is an app bundle to test.
Building the iOS Application
Select Build > Rebuild All to rebuild the project. For an iOS application, we must always use a DEBUG build of the
application that has the Xamarin Test Cloud Agent linked in to the application. Recall from the previous section that the
Xamarin.iOS linker will strip out the Test Cloud Agent in a RELEASE build.
Using the REPL
With the application built, select Run > Run Unit Tests from the Xamarin Studio menu. When you do this, Xamarin Studio
and UITest will take the following steps:
Run the test.
Start up the simulator.
Install the app bundle (if it is not already installed).
Start the application.
When the _app.Repl() line is encountered the test will pause executing and bring up the REPL, as shown in the
following screenshot:
Xamarin.UITest will not update the app bundle on the device with (it will always install the app bundle if it is not present). If
you make changes to the app bundle while writing UITests, you must manually uninstall the application from the simulator
and recompile the iOS application prior to running UITests again.
Exploring and Interacting with the User Interface
With the test paused and the REPL active, we can get a text representation of the user interface by using the tree
command in the REPL, as shown in the following screenshot:
It can be useful to cut and paste the results of the tree command into a text editor for reference:
[UIWindow > UILayoutContainerView]
[UINavigationTransitionView > ... > UIView]
[UITextField] id: "CreditCardTextField"
[UIButton] id: "ValidateButton"
[UIButtonLabel] text: "Validate Credit Card"
[UILabel] id: "ErrorMessagesTextField"
[UINavigationBar] id: "Credit Card Validation"
[_UINavigationBarBackground]
[_UIBackdropView > _UIBackdropEffectView]
[UIImageView]
[UINavigationItemView]
[UILabel] text: "Credit Card Validation"
Now that we have this this information, we can begin experimenting with the queries that our first test needs. Lets start by
entering a 15-character number into the CreditCardTextField. In the REPL, type the following:
>>> app.EnterText(c=>c.Marked("CreditCardTextField"), "123456789012345")
When you hit Enter, you should see the iOS keyboard pop up in the simulator, and the number being entered. The REPL
and simulator should look similar to the following screenshot:
Next we will simulate touching the button; type the following in the REPL and hit Enter:
>>> app.Tap(c=>c.Marked("ValidateButton"))
Now the REPL and simulator should look something like this:
The UI has changed, so we need run tree again to see the new view hierarchy. If we examine the output of tree
command, we can see a UILabel with our error message. We will need a query to locate the UILabel with the text of
the error message. This is that query:
c=>c.Marked("ErrorMessagesTextField").Text("Credit card number is too short.")
The method IApp.Flash will flash views on the screen, this makes it very helpful for verifying that you have the correct
query for a given view. Let's type that in the REPL to make sure that we do have the correct query to reference that the
view containing the error message:
>>> app.Flash(c=>c.Marked("ErrorMessagesTextField").Text("Credit card number is too
short."))
Flashing query for Marked("ErrorMessagesTextField").Text("Credit card number is too
short.") gave 1 results.
[
{
"Id": null,
"Description": "<UILabel: 0x7b5f8720; frame = (10 210; 300 40); text = 'Credit card
number is to ...'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7b5f8860>>",
"Rect": null,
"Label": null,
"Text": null,
"Class": null,
"Enabled": false
}
]
You should see the UILabel flash several times on the screen. At this point we have everything we need to write the
code for the test, so let's do that next.
Writing the First Test
To write the test, we need the queries that we entered the the REPL. Fortunately for us, the REPL remembers all of the
commands we typed in. They are accessible using the copy command which will fill the clipboard with what we have
typed in the REPL. Lets use this to extract our work from the REPL finish up the first test. Type the following into the
REPL:
>>> copy
Copying history to clipboard.
>>> exit
At this point the REPL will exit, and we should have the following in the clipboard buffer:
app.EnterText(c=>c.Marked("CreditCardTextField"), "123456789012345");
app.Tap(c=>c.Marked("ValidateButton"));
app.Flash(c=>c.Marked("ErrorMessagesTextField").Text("Credit card number is too short."));
Return to the Xamarin Studio and open up the ValidateCreditCardTests class. Paste the contents of the clipboard
into our first test, and edit it to resemble the following:
[Test]
public void CreditCardNumber_TooShort_DisplayErrorMessage()
{
/* Arrange - set up our queries for the views */
ConfigureTest();
_app.EnterText(c=>c.Marked("CreditCardTextField"), "123456789012345");
/* Act - enter the credit card number and tap the button */
_app.Tap(c=>c.Marked("ValidateButton"));
/* Assert - make sure that the error message is displayed. */
AppResult[] result = _app.Query(c=>c.Marked("ErrorMessagesTextField").Text("Credit
card number is too short."));
Assert.IsTrue(result.Any(), "The 'short credit card' error message is not being
displayed.");
}
Notice that we replaced the Flash command with the Query command. The Query command will return a list of all
views that match the query results. If for some reason the UILabel with the error message does not appear on the
screen _app.Query then AppResult[] will have zero elements in it and the Assert will fail the test. If you run the test,
it should pass, and you should see the following output in the Test Results pad:
Create the Second and Third Tests
With the first test written, we can go ahead and finish up two more tests as they are very similar to. We can cut and paste
the first test two more times, and by making some slight changes to the test name and the queries we will have the next
two tests done.
Here is the test for when the user doesn't enter a credit number at all, i.e. they just tap the Validate Button:
[Test]
public void CreditCardNumber_IsBlank_DisplayErrorMessage()
{
/* Arrange - set up our queries for the views */
ConfigureTest();
_app.EnterText(c=>c.Marked("CreditCardTextField"), string.Empty);
/* Act - enter the credit card number and tap the button */
_app.Tap(c=>c.Marked("ValidateButton"));
/* Assert - make sure that the error message is displayed. */
AppResult[] result = _app.Query(c=>c.Marked("ErrorMessagesTextField").Text("This is
not a credit card number."));
Assert.IsTrue(result.Any(), "The 'missing credit card' error message is not being
displayed.");
}
Here is the test for when the credit card number is too long:
[Test]
public void CreditCardNumber_TooLong_DisplayErrorMessage()
{
/* Arrange - set up our queries for the views */
ConfigureTest();
_app.EnterText(c=>c.Marked("CreditCardTextField"), "12345678901234567");
/* Act - enter the credit card number and tap the button */
_app.Tap(c=>c.Marked("ValidateButton"));
/* Assert - make sure that the error message is displayed. */
AppResult[] result = _app.Query(c=>c.Marked("ErrorMessagesTextField").Text("Credit
card number is too long."));
Assert.IsTrue(result.Any(), "The 'long credit card' error message is not being
displayed.");
}
If we run all these tests in Xamarin Studio, they should all pass. The following is a screenshot of the the Test Result pad in
Xamarin Studio showing the three successful tests:
At this point there is one test remaining – the test for a 16-digit credit card number. Lets cover this test in the next section.
Finishing up the Final Test
The final scenario we need to test is when the user enters a 16-digit credit card number. The test is very similar, but with
one big exception – it has to pause for the validation screen to finish loading before confirming that the success message
is displayed. The IApp.WaitForElement method will pause the test until a specific view appears on the screen. You
can see how to use this method in the following test:
[Test]
public void CreditCardNumber_CorrectSize_DisplaySuccessScreen()
{
// Arrange - set up our queries for the views
ConfigureTest();
_app.EnterText(c=>c.Marked("CreditCardTextField"), "1234567890123456");
/* Act - enter the credit card number and tap the button */
_app.Tap(c=>c.Marked("ValidateButton"));
_app.WaitForElement(c => c.Class("UINavigationBar").Id("Valid Credit Card"), "Valid
Credit Card screen did not appear", TimeSpan.FromSeconds(5));
/* Assert */
AppResult[] results = _app.Query(c => c.Marked("CreditCardIsValidLabel").Text("The
credit card number is valid!"));
Assert.IsTrue(results.Any(), "The success message was not displayed on the screen");
}
In this example, WaitForElement will allow up to five seconds for a UINavigationBar with an Id of Valid Credit
Card to appear on the screen. If this does not happen then an exception will be thrown, and the test will fail. The test will
then assert that the success message is displayed.
This is all the tests we need to write for this particular application. If we run all the unit tests, they should all pass as,
similar to the following screenshot:
Congratulations! At this point you have written your first set of UITests – for iOS. The next thing to do is to create some for
the Android version of the application. We could repeat the process here for the Android tests, but that would be a bit
wasteful. With some refactoring we can share the same test fixture for both the iOS and Android application. Lets move
on to the next section and go over the changes.