Faking mouse click behavior in OnGUI via Event.current

I’m writing an EditorGUI field for my custom serialized field, so I’m trying to mimic that API.

Here’s an example of what I’m trying to do:

public static Rect MyField(Rect position)
{
    var evt = Event.current;
    if (evt.type == EventType.MouseDown)
        Debug.Log("MyField clicked!");
}

I’m trying to test it, with a TestEditorWindow and onGui callbacks:

public class TestEditorWindow : EditorWindow
{
    public Action onGui;

    void OnGUI() => onGui?.Invoke();
}
[UnityTest]
public IEnumerator TestMyField()
{
    bool onGuiCalled = false;
    var position = new Rect(x: 0, y: 0, width: 150, height: 30);
    void OnGUI()
    {
        var evt = new Event() { type = EventType.MouseDown, clickCount = 1, mousePosition = position.center };
        Event.current = evt;
        MyField(position);
        onGuiCalled = true;
    }
    var testWindow = EditorWindow.GetWindow<TestEditorWindow>();
    testWindow.onGui = OnGUI;
    yield return new WaitUntil(() => onGuiCalled);
    // Assert for MyField behavior here...
}

The issue I run into is that the EventType is changed to EventType.Ignored. I realize this is probably pretty poor testing behavior because the Layout and Repaint current events are overwritten.

I was thinking there was some behavior in Event.current that was validating the set event, so I tried changing my custom field to look like

public static Rect MyField(Rect position, Event customEvent = null)
{
    var evt = customEvent ?? Event.current;
    if (evt.type == EventType.MouseDown)
        Debug.Log("MyField clicked!");
}

And I changed the OnGUI method in my test to look like:

void OnGUI()
{
    var evt = new Event() { type = EventType.MouseDown, clickCount = 1, mousePosition = position.center };
    MyField(position, evt);
    onGuiCalled = true;
}

But somehow, the event type is still changed to EventType.Ignored. I tried debugging the creation of the event, and the event type is changed when it’s passed to MyField. I have no clue how this is happening.

After this, I was thinking that I probably shouldn’t be trying to set the current event while the OnGUI method is being called. I reverted the changes I made earlier, and tried the following:

[UnityTest]
public IEnumerator TestMyField()
{
    bool onGuiCalled = false;
    var position = new Rect(x: 0, y: 0, width: 150, height: 30);
    void OnGUI()
    {
        Event.current = evt;
        MyField(position);
        onGuiCalled = true;
    }
    var testWindow = EditorWindow.GetWindow<TestEditorWindow>();
    testWindow.onGui = OnGUI;
    var evt = new Event() { type = EventType.MouseDown, clickCount = 1, mousePosition = position.center };
    Event.current = evt;
    yield return new WaitUntil(() => onGuiCalled);
    // Assert for MyField behavior here...
}

However, the OnGUI method never received the set Event.current. No amount of yield return null and combinations of WaitUntil could get the OnGUI method to receive the event.

So I tried digging through the API one more time, and stumbled upon a method, EditorWindow.SendEvent. It looked promising–perhaps this was the way I could invoke an event in an editor window.

[UnityTest]
public IEnumerator TestMyField()
{
    bool onGuiCalled = false;
    var position = new Rect(x: 0, y: 0, width: 150, height: 30);
    void OnGUI()
    {
        Event.current = evt;
        MyField(position);
        onGuiCalled = true;
    }
    var testWindow = EditorWindow.GetWindow<TestEditorWindow>();
    testWindow.onGui = OnGUI;
    var evt = new Event() { type = EventType.MouseDown, clickCount = 1, mousePosition = position.center };
    testWindow.SendEvent(evt);
    yield return new WaitUntil(() => onGuiCalled);
    // Assert for MyField behavior here...
}

Nope! Somehow, EditorWindow.SendEvent automatically sets the EventType to EventType.Used.

I’ve been bashing my head at this problem for a couple hours now, and I need some help.

  • How the heck am I supposed to mock a simple MouseDownEvent at a position in IMGUI?
  • What’s causing my mouse down events set to Event.current to become ignored?
  • What magical being is causing the mouse down events passed to MyField to become ignored when they’re of type MouseDown after creation, and I don’t touch them in my editor script?

Sincerely, a Unity Dev with hopes of 100% Code Coverage.

To anyone wondering how I did it, I ended up doing the following:

Created my own GUIEvent valuetype:

public struct GUIEvent
{
    public EventType Type { get; }
 
    public GUIEvent(EventType type)
    {
        Type = type;
    }
}

Added an overload MyField method:

public static Rect MyField(Rect position)
{
    var evt = Event.current;
    return MyField(position, new GUIEvent(evt.type));
}

public static Rect MyField(Rect position, GUIEvent guiEvent)
{
    if (guiEvent.Type == EventType.MouseDown)
        Debug.Log("MyField clicked!");
    return position;
}

This gives me full control over the events I pass in. I still used the TestClass, but instead of calling MyField(Rect position), I called the overload MyField(Rect position, GUIEvent guiEvent). I have a separate test that tests the functionality of mapping Event.current to a GUIEvent. 100% Code Coverage is achievable!

I mean… code coverage has its faults, but it’s good to be able to know how to test all the things ;).