Search Unity

Async await in Unittests

Discussion in 'Editor & General Support' started by ISeidingerReflekt, Jan 23, 2018.

  1. ISeidingerReflekt

    ISeidingerReflekt

    Joined:
    Jun 2, 2017
    Posts:
    5
    I am struggling with writing unittests for async methods since the unittest itself can not be async. I am trying to work around this by using task.Wait() but this seems to result in a deadlock in certain situations.
    See the following code:
    Code (CSharp):
    1.  
    2.  
    3.         [Test]
    4.         public void TestAwait()
    5.         {
    6.             var task = GetTestTask();
    7.             task.Wait();
    8.             Assert.AreEqual(1, 1);
    9.         }
    10.  
    11.         private async Task GetTestTask()
    12.         {
    13.             UnityEngine.Debug.Log("1");
    14.             await Task.Delay(TimeSpan.FromMilliseconds(200));
    15.             UnityEngine.Debug.Log("2");
    16.             await Task.Run(async () => await Task.Delay(TimeSpan.FromSeconds(1)));
    17.             UnityEngine.Debug.Log("3");
    18.             await Task.Delay(TimeSpan.FromMilliseconds(200));
    19.             UnityEngine.Debug.Log("4");
    20.         }
    I guess that some of the code in GetTestTask will run on a different thread and try to sync up with the main thread while the mainthread is blocked because of task.Wait().
    How can i work around this issue, and is this a bug or a feature? ;)
    Any help appreciated!
     
  2. GoodGoodMan

    GoodGoodMan

    Joined:
    May 10, 2018
    Posts:
    1
    I have same problem.

    I also tried the following way.

    [Test]
    public void AwaitTest()
    {
    Task<bool> task = GetTask(); // Task will be fired from GetTask method.
    while(!task.IsCompleted) {}
    Assert.IsTrue(task.Result);
    }


    But it doesn't work.
     
  3. tsibiski

    tsibiski

    Joined:
    Jul 11, 2016
    Posts:
    275
    Alternatively, you could use the Trilleon Automation Framework that supports both Unit Testing and Integration Automation tests. It uses Coroutines to simulate asynchronocity, but it does not use multi threading or actual async (with a socket connection for server communication being the exception to that). But given your sample, it doesn't look as if you need real async. You just need something that can wait for work to be done before continuing the test execution. (Trilleon uses "WaitFor" which passes in a predicate that can express any boolean state, such as "is this work done yet?").

    You can try it out from here. https://github.com/disruptorbeam/trilleon.

    It is an entire framework, and will take care of this problem for you. However, your Unit Tests written in Trilleon will not run on compilation. It is not designed identically to something like NUnit. It however allows you to unit test IEnumerators and anything that requires gameplay context to validate.
     
  4. StephenHodgson-Valorem

    StephenHodgson-Valorem

    Joined:
    Mar 8, 2017
    Posts:
    148
    I'm surprised that just using `public async Task MyAysyncTest()` doesn't work out of the box with NUnit
     
  5. xmedeko

    xmedeko

    Joined:
    Jun 6, 2018
    Posts:
    23
    Last edited: Feb 20, 2019
  6. Mathijs_Bakker

    Mathijs_Bakker

    Joined:
    Apr 28, 2014
    Posts:
    21
    Unity uses an old Nunit framework. I don't know which version. But it does not offer proper async Task support.
    You can use a workaround that’s inefficient but works: Execute the async test logic on a different thread pool thread, and then (synchronously) block the unit test method until the actual test completes.

    The example test below will fail nicely:

    Code (CSharp):
    1. [Test]
    2. public void TestAwait()
    3. {
    4.     Task.Run(async () =>
    5.     {
    6.         await GetTestTaskAsync();
    7.     }).GetAwaiter().GetResult();
    8.    
    9.     Assert.AreEqual(1, 2);
    10. }
    11.  
    12. public async Task GetTestTaskAsync()
    13. {
    14.     Debug.Log("1");
    15.     await Task.Delay(TimeSpan.FromMilliseconds(200));
    16.     Debug.Log("2");
    17.     await Task.Run(async () => await Task.Delay(TimeSpan.FromSeconds(1)));
    18.     Debug.Log("3");
    19.     await Task.Delay(TimeSpan.FromMilliseconds(200));
    20.     Debug.Log("4");
    21. }
    22.  

    TestAwait (1.427s)

    Message:
    Expected: 2
    But was: 1

    Output:
    1
    2
    3
    4
     
    Last edited: Apr 22, 2019
    Red_Dragon69 likes this.
  7. xmedeko

    xmedeko

    Joined:
    Jun 6, 2018
    Posts:
    23
    @Mathijs_Bakker yes it works! Thanks.

    A bit simplified it is:
    Task.Run(() => GetTestTaskAsync()).GetAwaiter().GetResult();
     
    Last edited: Apr 26, 2019
    Mathijs_Bakker and Red_Dragon69 like this.
  8. pld

    pld

    Joined:
    Dec 12, 2014
    Posts:
    7
    Here is the pattern I use; it avoids deadlock if the code you're testing needs to wait for work that's posted to the Unity SynchronizationContext.

    Code (CSharp):
    1.   [UnityTest]
    2.   public IEnumerator TestSomeAsyncThing() {
    3.     var task = Task.Run(async () => {
    4.       /* non-boilerplate code goes here */
    5.     });
    6.     while (!task.IsCompleted) { yield return null; }
    7.     if (task.IsFaulted) { throw task.Exception; }
    8.   }
    9.  
    Edit: to propagate exceptions
     
    Last edited: Nov 27, 2019