Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

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:
    569
    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
     
    Huszky and sandolkakos like this.
  5. xmedeko

    xmedeko

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

    Mathijs_Bakker

    Joined:
    Apr 28, 2014
    Posts:
    25
    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
  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
    JakHussain, joaoborks and SDudzic like this.
  9. JakHussain

    JakHussain

    Joined:
    Oct 20, 2016
    Posts:
    318
    I see your very useful boiler plate and raise you a static method:

    Code (CSharp):
    1. public static IEnumerator Execute (Task task)
    2. {
    3.     while (!task.IsCompleted) { yield return null; }
    4.     if (task.IsFaulted) { throw task.Exception; }
    5. }
    Used like this:

    Code (CSharp):
    1. [UnityTest]
    2. public IEnumerator AddReceiver ()
    3. {
    4.     yield return AsyncTest.Execute (manager.AddReceiverAsync (workingCredentials.streamID));
    5.  
    6.     Assert.True (manager.ReceiverCount == 1);
    7. }
     
  10. jBardoe

    jBardoe

    Joined:
    Aug 18, 2019
    Posts:
    1
    @Mathijs_Bakker works like a charm!
    And if you need to get a result out of a task, it works in the similar way:
    (You can change UnityTest to just Test if your NUnit version requires it to be like that)
    Code (CSharp):
    1. [UnityTest]
    2. public void TestAwait()
    3. {
    4.     var task = Task.Run(async () =>
    5.     {
    6.         return await GetTestTaskAsync();
    7.     });
    8.  
    9.     Assert.AreEqual(1, task.Result);
    10. }
    11.  
    12. public async Task<int> GetTestTaskAsync()
    13. {
    14.     await Task.Delay(TimeSpan.FromMilliseconds(200));
    15.     return 1;
    16. }
    17.  
     
  11. Mathijs_Bakker

    Mathijs_Bakker

    Joined:
    Apr 28, 2014
    Posts:
    25
  12. AlexStark_

    AlexStark_

    Joined:
    Oct 19, 2018
    Posts:
    2
    Based on @Mathijs_Bakker I write some generic helper methods

    Code (CSharp):
    1.  
    2. public static class UnityTestUtils {
    3.  
    4.         public static T RunAsyncMethodSync<T>(Func<Task<T>> asyncFunc) {
    5.             return Task.Run(async () => await asyncFunc()).GetAwaiter().GetResult();
    6.         }
    7.         public static void RunAsyncMethodSync(Func<Task> asyncFunc) {
    8.             Task.Run(async () => await asyncFunc()).GetAwaiter().GetResult();
    9.         }
    10. }
    Usage:
    Code (CSharp):
    1.      
    2.         [Test]
    3.         public void Test()
    4.         {
    5.             var result = RunAsyncMethodSync(() => GetTestTaskAsync(4));
    6.             Assert.That(result, Is.EqualTo(4));
    7.         }
    8.  
    9.         public async Task<int> GetTestTaskAsync(int a) {
    10.             await Task.Delay(TimeSpan.FromMilliseconds(200));
    11.             return a;
    12.         }
    13.  
    14.         [Test]
    15.         public void Testthrow() {
    16.             Assert.Throws<InvalidOperationException>(
    17.                            ()=> RunAsyncMethodSync(() => ThrowTaskAsync(4)));
    18.         }
    19.  
    20.         public async Task<int> ThrowTaskAsync(int a) {
    21.             await Task.Delay(TimeSpan.FromMilliseconds(200));
    22.             throw new InvalidOperationException();
    23.         }
     
    Last edited: Jul 2, 2020
    sandolkakos, creepteks and XLoad like this.
  13. Rib

    Rib

    Joined:
    Nov 7, 2013
    Posts:
    39
    Here's _another_ :) variation that I arrived at...


    I have these static Await utilities, similar to @pid and @JakHussain ...
    Code (CSharp):
    1.  
    2.         public static IEnumerator Await(Task task)
    3.         {
    4.             while (!task.IsCompleted) { yield return null; }
    5.             if (task.IsFaulted) { throw task.Exception; }
    6.         }
    7.         public static IEnumerator Await(Func<Task> taskDelegate)
    8.         {
    9.             return Await(taskDelegate.Invoke());
    10.         }
    11.  
    Which can be used to 'await' for an async delegate or an async Task within a coroutine like...
    Code (CSharp):
    1.  
    2.         [UnityTest]
    3.         public IEnumerator TestSomeAsyncThing() {
    4.             yield return Await(async () => {
    5.                 await UniTask.Delay(TimeSpan.FromSeconds(5)); // example async thing
    6.             });
    7.         }
    8.         async Task AsyncTestMethod()
    9.         {
    10.             await UniTask.Delay(TimeSpan.FromSeconds(5)); // example async thing
    11.         }
    12.         [UnityTest]
    13.         public IEnumerator TestSomeOtherAsyncThing() {
    14.             yield return Await(AsyncTestMethod());
    15.         }
    16.  

    The main reasons I went with this approach:
    1) Calling GetResult() or reading Result, as above, blocks the main thread which is the thread most of the Unity code I want to test is designed to run from.
    2) It avoids using Task.Run() (which will run code via a thread pool), again because most of the Unity code I'm looking to test is designed to run on the main thread (or will handle spawning work on threads itself where appropriate)
    3) The static Await() utilities feel similar to using the await keyword but within a coroutine
    4) It supports the convenience of using an async lambda for writing tests
     
  14. ModLunar

    ModLunar

    Joined:
    Oct 16, 2016
    Posts:
    372
    Last edited: Mar 30, 2021
  15. thewerku

    thewerku

    Unity Technologies

    Joined:
    Feb 6, 2020
    Posts:
    15
    Hi. Team owning the Unity Test Framework package (UTF, also known as Test Runner) has recently made available a pre-release version of v2.0 - you can read the announcement here. One of the new features we added, and are looking to get users' eyes on it, are async tests. We'd appreciate any feedback and comments, if you decide to test it out. Thanks!
     
    ModLunar likes this.
  16. ModLunar

    ModLunar

    Joined:
    Oct 16, 2016
    Posts:
    372
    Thanks so much for sharing!
    I'll take a look at this soon, for feedback, would replying to this thread, or somewhere else be preferred?
     
  17. thewerku

    thewerku

    Unity Technologies

    Joined:
    Feb 6, 2020
    Posts:
    15
    ModLunar likes this.