0

For my company I'm creating an Azure Functions with .NET Core 3.x to generate the invoices. I have to consider different scenarios and I want to create a test for that.

I thought to create a function where I pass a list of lines for the invoice. In this function I have to check if there is any refund for the user: if so, I have to deduct the amount from the current invoice. If some money to refund are left, I have to create a new refund invoice.

public class InvoiceGeneratorResponse
{
    public List<InvoiceGeneratorDetailResponse> Details { get; set; }
    public List<InvoiceGeneratorErrorResponse> Errors { get; set; }
    public List<InvoiceGeneratorNextInvoiceResponse> NextInvoice { get; set; }
}

InvoiceGeneratorResponse is the result of my class where Details is the invoice list, Errors is the list of incomplete invoices and NextInvoice is the list of refund invoices I have to create.

Now, I want to create a bunch of tests to check every scenario. For that, I have to pass the list of rows to this function and check the result.

I was thinking to read the list of rows from a json file and convert it in a list to pass into the function. For the result I want to read another file, convert it in a list and compare the list from the file with the result from the function.

Is there already an annotation for reading a file in a test? If yes, with what framework? xUnit? nUnit? Is there an easy way to fully compare two lists in a test?

Enrico
  • 3,592
  • 6
  • 45
  • 102
  • Dear user, it's better you show us your current code rather than explaining it. Please provide code so that we can help you sooner and better. – Mahdi Tahsildari Aug 03 '20 at 09:44

4 Answers4

3

Is there already an annotation for reading a file in a test? If yes, with what framework? xUnit? nUnit?

Not that I know of, and it would have to be a very particular format that serves some purposes but lacks it for others. So if a framework would be able to read JSON, they'll have to support that as well, and requests will come in to also support XML and OpenXML and so on, so I could see why testing frameworks don't support this out of the box.

You can however use attributes to supply input parameters for your tests using various frameworks:

MSTest ("data-driven tests") with the values in attributes:

[DataTestMethod]
[DataRow(12,3,4)]
[DataRow(12,2,6)]
[DataRow(12,4,3)]
public void DivideTest(int n, int d, int q)
{
     Assert.AreEqual(q, n / d);
}

NUnit ("parameterized tests") with the values in an array:

[TestCaseSource("DivideCases")]
public void DivideTest(int n, int d, int q)
{
    Assert.AreEqual(q, n / d);
}

static object[] DivideCases =
{
    new object[] { 12, 3, 4 },
    new object[] { 12, 2, 6 },
    new object[] { 12, 4, 3 }
};

And xUnit can be extended to read data from a JSON file specified in an attribute so on.

But in your case, if you simply want to prepare a JSON file that contains the desired output of your method, you could read that yourself using Newtonsoft.Json and CollectionAssert.AreEqual(expected, actual, comparer) using your own comparer for InvoiceGeneratorDetailResponse:

public void Foo_Bars_The_Foo()
{
    // Arrange
    var classUnderTest = ...
    var expectedJson = File.ReadAllText("bar_output.json");
    var expected = JsonConvert.DeserializeObject<List<InvoiceGeneratorDetailResponse>>(expectedJson);
    
    // Act
    var result = classUnderTest...  
    
    // Assert
    var invoiceDetailComparer = new ...
    CollectionAssert.AreEqual(expected, result, invoiceDetailComparer);
}
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
1

Is there an easy way to fully compare two lists in a test?

Yes, there is an easy way provided by NUnit - you can check for equivalency of the collections:

[Test]
public void ListCompareTest()
{
    List<string> expectedResult = new List<string>
    {
        "someData1", "someData2", "someData3", "someData4"
    };

    // you do something here with your methods and here comes actual result of that work

    List<string> actualResult = new List<string>
    {
        "someData1", "someData2", "someData3", "someData4"
    };

    Assert.That(actualResult, Is.EquivalentTo(expectedResult));
}

Also, you can use custom equality comparer for this purpose or your function to compare the results:

// custom comparer
Assert.That(actualResult, Is.EquivalentTo(expectedResult)
    .Using(StringComparer.OrdinalIgnoreCase as IComparer));
// custom function to compare the results
Assert.That(actualResult, Is.EquivalentTo(expectedResult)
    .Using<string, string>((left, right) => Regex.IsMatch(left, right)));
0

Unit tests are meant to be fast. So you need to detect dependencies and simulate them.

For the file question, I would create a Mock object (using Moq with nUnit) to simulate the file reading process. I believe you don't want to test the reading filesystem part.
You should mock the input (file) and only test your logic.

And for the question about comparing two lists, you can check CollectionAssert which has some great features. If that does not do your requirements, then you need to override Equals on your class.

Mahdi Tahsildari
  • 13,065
  • 14
  • 55
  • 94
  • 1
    They don't want to test the file reading part, they want to read a file that contains input and output for their test. – CodeCaster Aug 03 '20 at 09:58
  • @CodeCaster I understand that. I mean we are sure that the file will be read fine. What he needs is the data (input & output) in the file. So he does not need to test the file. In his test method, he should pass the data to the sut (system under test) and check the results. – Mahdi Tahsildari Aug 03 '20 at 10:05
  • 1
    You don't appear to understand that, as your entire second paragraph negates that. They don't have file reading code in their SUT. They're asking for an attribute or other helper that can read a file with test data and pass that to the unit they want to test. – CodeCaster Aug 03 '20 at 10:06
  • I believe he should not do so. Unit tests are meant to be fast... we should not bind them to read file stuff (hence we should not read their input/output data from a file). He could write his test with a bunch of sample data that satisfies his needs. What you are referring is a kind of E2E (end-to-end) testing. – Mahdi Tahsildari Aug 03 '20 at 10:09
  • We do not provide fresh data at runtime to our Unit Tests. Instead, we provide hard-coded sample data. If we provide data with enough variety to make sure every different situation is targeted, that will do. A test method should not run against a file at all. It should always check the same data, why? Because if it was working yesterday with data A,B,and C, it should work with that data again today and tomorrow. If today it runs with an error for say B, then it means I have broken some previously-working code. – Mahdi Tahsildari Aug 03 '20 at 10:13
  • It is a very common practice to put test data in external files, because it is unmaintainable to have them in attributes or dedicated classes in the source code. Reading a few KB of text at most from a file is fast enough to not be noticeable on a bigger test project. Reading **test** data from a file is not end-to-end testing. – CodeCaster Aug 03 '20 at 12:39
0

I found a solution to my question. I saw how to read data from a file but it doesn't work for complex object like, in my case, an invoice. I found that FluentAssertions has a specific package for comparing json files. called FluentAsertions.Json.

FluentAsertions.Json

Based on that, I have created my tests as following:

[Test]
public void Check_Result_With_One_Organization_Simple()
{
    List<Organisation> invoice = new FileService<List<Organisation>>()
                                         .GetData("Data\\invoice-in-1.json");
    InvoiceGeneratorResponse expected = new FileService<InvoiceGeneratorResponse>()
                                         .GetData("Data\\invoice-out-1.json");

    InvoiceGeneratorResponse response = _service.GenerateAndSaveInvoices(invoice);

    JToken jExpected = JToken.Parse(JsonConvert.SerializeObject(expected));
    JToken jResponse = JToken.Parse(JsonConvert.SerializeObject(response));

    jResponse.Should().BeEquivalentTo(jExpected);

    Assert.AreEqual(expected.NumberOfErrors, response.NumberOfErrors);
    Assert.AreEqual(expected.NumberOfInvoices, response.NumberOfInvoices);
    Assert.AreEqual(expected.NumberOfOrganisations, response.NumberOfOrganisations);

    Assert.AreEqual(expected.Total, response.Total);
    Assert.AreEqual(expected.TotalAmount, response.TotalAmount);
    Assert.AreEqual(expected.TotalTaxes, response.TotalTaxes);
}

FileService is my simple class to read a json file from the filesystem and returns an object in T.

/// <summary>
/// Class FileService.
/// </summary>
/// <typeparam name="T"></typeparam>
public class FileService<T> where T : class
{
    /// <summary>
    /// Gets the data.
    /// </summary>
    /// <param name="filename">The filename.</param>
    /// <returns>T.</returns>
    public T GetData(string filename)
    {
        var datasetJson = File.ReadAllText(filename);
        var dataset = JsonConvert.DeserializeObject<T>(datasetJson);
        return dataset;
    }
}

The magic is in this line

jResponse.Should().BeEquivalentTo(jExpected);

Here FluentAsertions.Json checks the json files and gives you if those files and exactly the same structure and if not gives you same details.

FluentAsertions.Json

Enrico
  • 3,592
  • 6
  • 45
  • 102