13

I have a unit test project using xUnit.net v.2.3.1 for my ASP.NET Core 2.0 web app.

My test should focus on testing a given DataEntry instance: DataEntry instances are generated by the async method GenerateData() in my DataService class, which looks like:

public class DataService {
    ...
    public async Task<List<DataEntry>> GenerateData() {
        ...
    }
    ...
}

I am writing this test case as a Theory so my test can focus on a DataEntry instance at a time. Here is the code:

[Theory]
[MemberData(nameof(GetDataEntries))]
public void Test_DataEntry(DataEntry entry) {

    // my assertions
    Assert.NotNull(entry);
    ...

}

public static async Task<IEnumerable<object[]>> GetDataEntries() {

    var service = new DataService();
    List<DataEntry> entries = await service.GenerateData().ConfigureAwait(false);

    return entries.Select(e => new object[] { e });

}

However, I get the following error at compile time:

MemberData must reference a data type assignable to 'System.Collections.Generic.IEnumerable<object[]>'. The referenced type 'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<object[]>>' is not valid.

From the error description, it seems xUnit.net does not allow MemberData to use an async static method, like my GetDataEntries() one. Is there any functionality gap in xUnit.net I should be aware of?

Now, I know I could switch my Theory into a Fact and loop through each DataEntry in the list returned by my DataService, however I would prefer to keep a Theory setup as my test would be cleaner and focused on DataEntry instead of List<DataEntry>.

Question: is there any way in xUnit.net to let my Theory get data from my DataService async API? Please, note the DataService class cannot be changed nor extended to provide data synchronously.

EDIT

I am looking for a way through async/await and would prefer to avoid any usage of blocking calls such as Task<T>.Result e.g. on my GenerateData() method, as the underlying thread will be blocked til the operation completes. This is relevant in my test project as I have other similar test cases where data should be retrieved in the same way and therefore I want to avoid ending up with too many blocking calls, but instead keeping the async/await propagation.

smn.tino
  • 2,272
  • 4
  • 32
  • 41
  • 1
    I don't know how this will play out, but try `.Result` on your `GenerateData`. – Paulo Morgado May 04 '18 at 12:02
  • thanks for the input, but I would actually prefer to find a way through async/await as, by using Task.Result, the underlying Thread will be blocked til the operation completes. In general, this would be fine if it was an isolated scenario, but I actually have several other similar test cases where I need to retrieve data from an asynchronous call like the one I posted and therefore I want to avoid ending up with too many blocking calls. – smn.tino May 04 '18 at 12:34
  • @PauloMorgado from your comment I just realized my question was not precise enough, hence I just edited. – smn.tino May 04 '18 at 12:41
  • Have you tried it? – Paulo Morgado May 05 '18 at 18:07
  • yes, I just tried and it works: using `.Result` removes the need of using Task as my static member GetDataEntries() would end up returning type IEnumerable instead of Task>. – smn.tino May 07 '18 at 06:54
  • 4
    issue reference in the xUnit repository: https://github.com/xunit/xunit/issues/1698 – smn.tino May 07 '18 at 09:24
  • AFAIK from reading [link](https://andrewlock.net/creating-parameterised-tests-in-xunit-with-inlinedata-classdata-and-memberdata/) there is no support for async methods and the error message supports my theory. You only can use "sync" methods. Perhaps look [link](https://stackoverflow.com/a/9343733/593045) how to get a synchronous method from an async method. – BitLauncher Feb 25 '21 at 21:08

2 Answers2

2

Until xUnit allows async theory data, you can use Task<T> instances as theory data and await them inside the test method (note that test methods can be async):

public static IEnumerable<object> GetDataEntries() {
    var service = new DataService();
    yield return new object[] { service.GenerateData() };
}

[Theory]
[MemberData(nameof(GetDataEntries))]
public async Task Test_DataEntry(Task<List<DataEntry>> task) {
    List<DataEntry> entries = await task;

    for (int i = 0; i < entries.Count; i++) {
        // my assertions
        Assert.NotNull(entries[i]);
    }
}
JosephDaSilva
  • 1,107
  • 4
  • 5
0

This functionality is not provided internally. You can try following:

  1. Write your CustomMemberDataAttribute by inheriting DataAttribute.
  2. Override 'GetData' method of parent class.
  3. Make the method async, that, provides data.
  4. Call async data provider method from 'GetData' method.
  5. Use your CustomMemberDataAttribute to decorate test cases.

You can refer following link to write your custom attribute. Keep other method same, just modify 'GetData' method as discuss above. https://github.com/xunit/xunit/blob/bccfcccf26b2c63c90573fe1a17e6572882ef39c/src/xunit.core/MemberDataAttributeBase.cs