views:

136

answers:

2

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).

A: 

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.

Joshua Cauble
I think this is a Bad Idea - there are far cleaner ways of managing this. Also you have a bug in your sample. If you read it, you'll see it. Bottom line on why I'm not even telling you what it is is that this level of complexity and hackery quite simply doesnt belong in a test. There should be 3As and it should be as short and straightforward as possible.
Ruben Bartelink
It's a code snippet not a complete class. It works when in a class, I made sure of that.
Joshua Cauble
Not suggesting you didnt test it or that you're just chucking stuff out. I know you went to a lot of effort. Saying that having complex Assert sections and logic is error prone. The error is in the following snippet:- `passesValidation = PassesAssert`. There is one further instance of the same issue. There are not 3 instances of the bug. Bottom line is if a test gets this complex, there is something wrong. (I read xUnit Test Patterns recently - its a great book, and the Osherove book can be good for getting thinging straight on this)
Ruben Bartelink
Ruben Bartelink
To be honest have not used bit operations that much so yeah I would have totally missed that. I see what you mean now in that it could reset it from false to true on a subsequent pass. :) I still like your Theory solution better. I don't like ugly unit tests either. They should be simple short and sweet.
Joshua Cauble
@Joshua Cauble: Can you fix the code or put a marker saying "this wont work" in case a drive-by copy and paster wastes time as a result?
Ruben Bartelink
+6  A: 

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 ) );
    }
}
Ruben Bartelink
I've just started using xUnit and have not looked into the Theory stuff yet but I agree that this is a much better way to do it.
Joshua Cauble
I highly recommend reading through all the articles and blog posts linked from xunit.codeplex.com - there's some great stuff there (and the discussions and features are great for discovering reasons why stuff *intentionally* isnt supported)
Ruben Bartelink