Xunit: Perform all 'Assert'ions in one test method?

1.7k views Asked by At

Is it possible to tell xUnit.net to perform all e.g. Assert.True() in one test method? Basically in some of our use/testcases all assertions belong logically to one and the same 'scope' of tests and I have e.g. something like this:

    [Fact(DisplayName = "Tr-MissImpl")]
    public void MissingImplementationTest()
    {
        // parse export.xml file
        var exportXml = Libraries.Utilities.XML.GenericClassDeserializer.DeserializeXmlFile<Libraries.MedTrace.ExportXml>(
                ExportXmlFile);

        // compare parsed results with expected ones
        Assert.True(exportXml.ContainsRequirementKeyWithError("PERS_154163", "E0032A"));
        Assert.True(exportXml.ContainsRequirementKeyWithError("PERS_155763", "E0032A"));
        Assert.True(exportXml.ContainsRequirementKeyWithError("PERS_155931", "E0032A"));
        Assert.True(exportXml.ContainsRequirementKeyWithError("PERS_157145", "E0032A"));

        Assert.True(exportXml.ContainsRequirementKeyWithError("s_sw_ers_req_A", "E0032A"));
        Assert.True(exportXml.ContainsRequirementKeyWithError("s_sw_ers_req_C", "E0032A"));
        Assert.True(exportXml.ContainsRequirementKeyWithError("s_sw_ers_req_D", "E0032A"));       
    }

Now if e.g. the first Assert.True(...) fails, the other ones are not executed/checked. I'd rather not break these seven Assertions up into separate methods, since these really do belong together logically (the TC only is 'passed' entirely if all seven are passing all together).

2

There are 2 answers

2
Ruben Bartelink On BEST ANSWER

The whole point of AAA is to aim to simplify each piece as much as possible and keep it focused, so readers can understand the tests quickly and isolate the cause of failures easily.

You have 7 different Facts here, which together comprise a Theory. So you should create a [Theory] with 7 sets of [InlineData] representing the expectations. See http://blog.benhall.me.uk/2008/01/introduction-to-xunitnet-extensions.html for an example.

If the re-executing of the Arrange/Act is an issue, then you should be making that the 'fixture' (in xUnit Test Patterns parlance) by doing that in the constructor of your test class.

public class WithGenericClassDeserializer
{
    var exportXml;

    public WithGenericClassDeserializer()
    {
        // Or move this into the GivenExportXmlFile_ExpectedValueAtKeyShouldMatch
        exportXml = Libraries.Utilities.XML.GenericClassDeserializer.DeserializeXmlFile<Libraries.MedTrace.ExportXml>( ExportXmlFile );
    }

    [Theory( DisplayName = "Tr-MissImpl" )]
    [InlineData( "PERS_154163", "E0032A" )]
    [InlineData( "PERS_155763", "E0032A" )]
    [InlineData( "PERS_155931", "E0032A" )]
    [InlineData( "PERS_157145", "E0032A" )]
    [InlineData( "s_sw_ers_req_A", "E0032A" )]
    [InlineData( "s_sw_ers_req_C", "E0032A" )]
    [InlineData( "s_sw_ers_req_D", "E0032A" )]
    public void GivenExportXmlFile_ExpectedValueAtKeyShouldMatch( string key, string value )
    {
        Assert.True( exportXml.ContainsRequirementKeyWithError( key, value ) );
    }
}
6
Joshua Cauble On

Yes you can but you have to add extra code to your test. Each Assert statement in xUnit will throw an exception if the assert fails. So in the case of Assert.True it throws a TrueException. So what you could do is something along the lines of:

    [Fact]
    public void MultipleAssertTest()
    {
        bool passesValidation = true;

        passesValidation = PassesAssert(true == true);
        passesValidation = PassesAssert(false == true);
        passesValidation = PassesAssert(5 == 1);

        Assert.True(passesValidation);

    }

    private bool PassesAssert(bool expression)
    {
        try
        {
            Assert.True(expression);
            return true;
        }
        catch
        {
            return false;
        }
    }

This will allow you to run all your asserts and do your test completely. However it is not very helpfull as you don't know what assert fails. What would be better is below so you can actually get results out of your test so you know which items fail.

       private StringBuilder resultMsg;
    [Fact]
    public void MultipleAssertTest()
    {
        bool passesValidation = true;
        resultMsg = new StringBuilder();

        passesValidation = PassesAssert(true == true, "True True test");
        passesValidation = PassesAssert(false == true, "False True test");
        passesValidation = PassesAssert(5 == 1, "5==1 Test");

        Assert.True(passesValidation, resultMsg.ToString());

    }

    private bool PassesAssert(bool expression, string message)
    {
        try
        {
            Assert.True(expression, message);
            return true;
        }
        catch (AssertException ex)
        {
            resultMsg.Append(ex.UserMessage);
            resultMsg.Append(Environment.NewLine);
            return false;
        }
    }

Of course this requires a little more work but it allows you to block all your Asserts together. You can wrap any of your test Assert methods this way. So you could say you are extending xUnit to have a non-failing Assert process.

Hope this helps.

EDIT: As per the comments in order for this to work you would need to use the & operator as the subsequent calls would overwrite the value.