UI testing

Testing UI behaviour is not an easy task. But there are multiple ways to do some useful tests without much effort.

Clicking UI

Usually as simple as finding references to UI. Might be harder at times. But usually finding by name should be enough. And if your project is big enough that finding by name is not an option or if the naming is bound to change. Fetch the references from any other type that you hold in the scene or use tags.

Once you have the reference to your UI item. You can execute Button.onClick.Invoke() and equivalent for sliders/inputfields.

1
2
3
4
5
6
7
8
9
10
11
[UnityTest]
public IEnumerator UserInterface_ClickButton()
{
SceneManager.LoadScene("UI_Button");
yield return null;
Button btn = GameObject.Find("CLICKME").GetComponent<Button>();
btn.onClick.Invoke();
Assert.IsFalse(btn.gameObject.activeInHierarchy, "The button should deactivate the gameobject it is attached to.");
}

Verifying element visibility

In my project I ran into problems where UI would go out of view for different resolutions. And in my particular example All my canvases should have been visible on screen if enabled.

First we make an extension method where we could test if one RectTransform contains another one. Methods that unity provides don’t take anchors and pivots into account.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static public class RectTransformExt
{
private static bool Fits(this Rect parent, Rect child)
{
return
parent.Contains(new Vector2(child.xMin + .5f, child.yMin + .5f)) &&
parent.Contains(new Vector2(child.xMin + .5f, child.yMax - .5f)) &&
parent.Contains(new Vector2(child.xMax - .5f, child.yMin + .5f)) &&
parent.Contains(new Vector2(child.xMax - .5f, child.yMax - .5f));
}
static public Rect GetRectRelativeToScreen(this RectTransform rectTransform)
{
Vector3[] corners = new Vector3[4];
rectTransform.GetWorldCorners(corners);
Vector2 size = new Vector2(corners[3].x - corners[0].x, corners[1].y - corners[0].y);
return new Rect(corners[0], size);;
}
public static bool Contains(this RectTransform rt, RectTransform rectTransform)
{
Rect parent = rt.GetRectRelativeToScreen();
Rect child = rectTransform.GetRectRelativeToScreen();
return Fits(parent, child);
}
}

Now going to the test…. We can’t change the resolution of game view by script so it should exclude editor. Then we provide resolution list from a static function. Fetch all canvases and go through all items. And done!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[UnityPlatform(exclude = new[] {RuntimePlatform.WindowsEditor, RuntimePlatform.LinuxEditor, RuntimePlatform.OSXEditor})]
[UnityTest]
public IEnumerator UserInterface_FitsInScreen([NUnit.Framework.ValueSource("SupportedResolutions")]string resolution)
{
string[] res = resolution.Split(new char[] {'x'}, StringSplitOptions.RemoveEmptyEntries);
Screen.SetResolution(int.Parse(res[0]),int.Parse(res[1]), Screen.fullScreenMode);
SceneManager.LoadScene("UI_Elements");
yield return null;
Debug.Log(Screen.currentResolution);
Canvas[] canvases = GameObject.FindObjectsOfType(typeof(Canvas)) as Canvas[];
foreach (var canvas in canvases)
{
RectTransform canvasTransform = canvas.GetComponent<RectTransform>();
foreach (RectTransform rectTransform in canvas.transform)
Assert.IsTrue(canvasTransform.Contains(rectTransform), string.Format("UI element named {0} is out of bounds.", rectTransform.name));
}
}

Hope you found something useful that you could apply in your adventures with Unity and its test runner.