Search Unity

How to change project based on whether we're running in playmode or running a PlayMode test

Discussion in 'Testing & Automation' started by Julien-Lynge, Jul 27, 2020.

  1. Julien-Lynge

    Julien-Lynge

    Joined:
    Nov 5, 2010
    Posts:
    142
    This is a bit of a complicated problem, but here's what I'm looking to do:

    I want to affect how my overall project runs based on whether it's being started normally (e.g. clicking play) vs being started via the Test Runner in PlayMode.

    In my case, I have application-level setup that's being done via static constructors and via the RuntimeInitializeOnLoadMethod attribute. From these, my application sets up core systems, does some dependency injection, sets up what's basically a service locator for MonoBehaviours, etc. The problem is, I don't want to kick this off automatically when I'm running tests. Instead, I want to create only the systems I need for the test - which is the whole point of using DI :). This will allow me to easily mock up systems and properly unit test and integration test items while having full control over the test.

    So, somehow my application needs to know not to run code in the case that it was kicked off from the Test Runner. How do I do that?​

    Here's my solution. I'm posting it in case it's useful to anyone else, and also in case someone else has a better idea for how to do it.

    It starts with IPrebuildSetup and IPostBuildCleanup. These are great as IPrebuildSetup runs before everything else, including static constructors. However, it runs so early that they run before the assembly is reloaded for the test. That means that, if you try to set a variable value, it won't persist into the actual test.

    So from there I set a PlayerPref (which I call "RuntimeTest") bool to true during the test, and back to false at IPostBuildCleanup.

    Then, I use a static constructor in a base class (that all of my runtime tests derive from) which affects the actual project. NOTE: in my case, Application is Havenly.Studio.Application, not UnityEditor.Application.

    Here's my code:

    Code (CSharp):
    1.     public abstract class RuntimeTestSuite : IPrebuildSetup, IPostBuildCleanup
    2.     {
    3.         [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
    4.         private static void SetApplicationShouldRun()
    5.         {
    6.             Application.ShouldRunSetup = !EditorPrefs.GetBool("RuntimeTest");
    7.         }
    8.  
    9.         public void Setup()
    10.         {
    11.             Debug.Log("Got test setup");
    12.             EditorPrefs.SetBool("RuntimeTest", true);
    13.         }
    14.  
    15.         public void Cleanup()
    16.         {
    17.             Debug.Log("Got test cleanup");
    18.             EditorPrefs.SetBool("RuntimeTest", false);
    19.         }
    20.     }
    Hope that helps someone else!

    Does anyone have any better ideas about how to do this that doesn't involve EditorPrefs? The only downsides I can see here are:
    • If the editor itself crashes during a test, the EditorPref persists
    • In a broader sense, EditorPrefs persist when you close the application. This seems a bit unsafe
    To help with this, I have a Debug message when the application starts and Application.ShouldRunSetup is true, just to make it obvious why the application isn't working.
     
  2. MajeureX

    MajeureX

    Joined:
    May 4, 2020
    Posts:
    13
    By default, Unity defines a constraint named UNITY_INCLUDE_TESTS any any test assembly definitions. In your C# scripts, you can use preprocessor directives to include or exclude certain code depending on whether a constraint is defined or not. In your case you could do this. Here's an example:

    Code (CSharp):
    1. public class Application
    2.  
    3. #if !UNITY_INCLUDE_TESTS
    4.     static {
    5.         // Do runtime stuff only
    6.     }
    7. #endif
    8.  
    9.    // ...
    10.  
    11. }