1

I have this integration test in Xunit.

[Fact]
public async Task AddCampaignAsync_GivenUniqueTitle_GivenGoodDates_ShouldPass()
{
    var campaign = new Campaign
    {
        Title = "Test",
        StartDate = new DateTime(2021, 6, 5),
        EndDate = new DateTime(2021, 6, 6)
    };

    using (var context = new CampaignDbContext(_options))
    {
        _campaignService = new CampaignService(context);

        var actualCampaign = await _campaignService.AddCampaignAsync(campaign);

        Assert.Equal(campaign.Title, actualCampaign.Title);
        Assert.Equal(campaign.StartDate, actualCampaign.StartDate);
        Assert.Equal(campaign.EndDate, actualCampaign.EndDate);
    }
}

and the code for AddCampaignAsync is below:

    public async Task<Campaign> AddCampaignAsync(Campaign campaign)
    {            
        if (campaign.EndDate != null) {
            if (campaign.StartDate > campaign.EndDate)
            {
                throw new Exception("The campaign start date cannot be greater than end date");
            }
        }
        
        await _context.Campaigns.AddAsync(campaign);
        await _context.SaveChangesAsync();
        return campaign;
    }

It works fine. But can I pass in a collection of campaign as test data instead of just having one campaign object as test data?

Amal K
  • 4,359
  • 2
  • 22
  • 44
Steve
  • 2,963
  • 15
  • 61
  • 133

2 Answers2

1

Firstly, remove the campaign from the test method and make it a parameter of the method. Then remove the Fact attribute and add the Theory attribute. The Fact attribute is usually used to test where a collection of data is not involved. Tests that need to run multiple times for different data is usually decorated with Theory:

[Theory]
public async Task AddCampaignAsync_GivenUniqueTitle_GivenGoodDates_ShouldPass(Campaign campaign)
{

    using (var context = new CampaignDbContext(_options))
    {
        _campaignService = new CampaignService(context);

        var actualCampaign = await _campaignService.AddCampaignAsync(campaign);

        Assert.Equal(campaign.Title, actualCampaign.Title);
        Assert.Equal(campaign.StartDate, actualCampaign.StartDate);
        Assert.Equal(campaign.EndDate, actualCampaign.EndDate);
    }
}

There are several options to provide test data to a test method.

1. Inheirt from TheoryData<T>

TheoryData is used to provide data for the parameters of test methods. It defines several generic overloads for varying method parameter lengths. AddCampaignAsync_GivenUniqueTitle_GivenGoodDates_ShouldPass has one parameter Campaign, so inherit from TheoryData<Campaign>:

public class CampaignData : TheoryData<Campaign>
{
    public CampaignData()
    {
        Add(new Campaign { Title = "Test1", StartDate = new DateTime(2021, 1, 5), EndDate = new DateTime(2021, 10, 6) });
        Add(new Campaign { Title = "Test2", StartDate = new DateTime(2021, 2, 5), EndDate = new DateTime(2021, 9, 6) });
        Add(new Campaign { Title = "Test3", StartDate = new DateTime(2021, 3, 5), EndDate = new DateTime(2021, 8, 6) });
        Add(new Campaign { Title = "Test4", StartDate = new DateTime(2021, 4, 5), EndDate = new DateTime(2021, 7, 6) });
    }
}

In the constructor, call the Add method to add Campaign objects to the collection. Now what you can do is use the ClassData attribute:

[Theory]
[ClassData(typeof(CampaignData))]
public async Task AddCampaignAsync_GivenUniqueTitle_GivenGoodDates_ShouldPass(Campaign campaign)
{

    using (var context = new CampaignDbContext(_options))
    {
        _campaignService = new CampaignService(context);

        var actualCampaign = await _campaignService.AddCampaignAsync(campaign);

        Assert.Equal(campaign.Title, actualCampaign.Title);
        Assert.Equal(campaign.StartDate, actualCampaign.StartDate);
        Assert.Equal(campaign.EndDate, actualCampaign.EndDate);
    }
}

Using ClassData(typeof(CampaignData)), you are telling xUnit that CampaignData class has all the parameters that this method needs to be called with.

2. Use MemberData

If you don't want to create a new class, you can also define the collection items as a static member of the same class. In the same class you have the AddCampaignAsync_GivenUniqueTitle_GivenGoodDates_ShouldPass method, define a static property or field like this. The type of this property is an IEnumerable<object[]>. It is a collection of object[] arrays. Each object array in the collection defines one parameter list to the test method, Your test method has one parameter Campaign campaign. So each object array can contain a Campaign object. Then on the AddCampaignAsync_GivenUniqueTitle_GivenGoodDates_ShouldPass add the MemberData attribute with the name of the property:

class YourTestClass
{
    public static IEnumerable<object[]> Campaigns => new List<object[]>
    {
        new object[]
        {
            new Campaign { Title = "Test1", StartDate = new DateTime(2021, 1, 5), EndDate = new DateTime(2021, 10, 6) },
            new Campaign { Title = "Test2", StartDate = new DateTime(2021, 2, 5), EndDate = new DateTime(2021, 9, 6) },
            new Campaign { Title = "Test3", StartDate = new DateTime(2021, 3, 5), EndDate = new DateTime(2021, 8, 6) },
            new Campaign { Title = "Test4", StartDate = new DateTime(2021, 4, 5), EndDate = new DateTime(2021, 7, 6) }
        }
    };


    [Theory]
    [MemberData(nameof(Campaigns))]
    public async Task AddCampaignAsync_GivenUniqueTitle_GivenGoodDates_ShouldPass(Campaign campaign)
    {

        using (var context = new CampaignDbContext(_options))
        {
            _campaignService = new CampaignService(context);

            var actualCampaign = await _campaignService.AddCampaignAsync(campaign);

            Assert.Equal(campaign.Title, actualCampaign.Title);
            Assert.Equal(campaign.StartDate, actualCampaign.StartDate);
            Assert.Equal(campaign.EndDate, actualCampaign.EndDate);
        }
    }
}

This will call the method AddCampaignAsync_GivenUniqueTitle_GivenGoodDates_ShouldPass with each of those Campaign objects in the object arrays of Campaigns property.

If you only have a couple of Campaign objects to test, Sergei's method of using the InlineData attribute is a good choice.

Reference: https://andrewlock.net/creating-strongly-typed-xunit-theory-test-data-with-theorydata/ (You can find more ways of doing this here)

Amal K
  • 4,359
  • 2
  • 22
  • 44
0

You can use data-driven tests.

For example:

[Theory]
[InlineData("Test1", "2021-06-05", "2021-06-06")]
[InlineData("Test2", "2021-06-07", "2021-06-08")]
public async Task AddCampaignAsync_GivenUniqueTitle_GivenGoodDates_ShouldPass(string title, DateTime startDate, DateTime endDate)
{
    var campaign = new Campaign
    {
        Title = title,
        StartDate = startDate,
        EndDate = endDate
    };
    ...

You can find more examples here: https://andrewlock.net/creating-parameterised-tests-in-xunit-with-inlinedata-classdata-and-memberdata/