mwhelan / Specify

Specify is an opinionated .Net Core testing library that builds on top of BDDfy from TestStack
http://specify-dotnet.readthedocs.org/
MIT License
20 stars 8 forks source link

Using ITestOutputHelper with xUnit 2 #11

Closed DamianReeves closed 8 years ago

DamianReeves commented 8 years ago

ScenarioFor does not properly implement IDisposable correctly, and the lack of a virtual Dispose implementation makes it impossible for inheritors to override Dispose.

mwhelan commented 8 years ago

How would you like to see it implemented? How are you wanting to use ScenarioFor? Can you provide me with an example?

DamianReeves commented 8 years ago

So I ended up using TearDown to accomplish what I was going to do in Dispose, so I guess this isn't really needed. (I guess I forgot about that).

mwhelan commented 8 years ago

Yes, TearDown is the intended hook for post-test code to run.

I hope TearDown was easy enough to find in the documentation? It is still on the old readme.io site and needs to be ported over to the new ReadTheDocs one.

DamianReeves commented 8 years ago

I think the issue was I was in a mode where I was blindly porting without thinking for a few minutes and then it was like 'duh... use TearDown'. I think this can be closed.

A totally unrelated issue however is that when using xUnit to get your test output to appear in Visual Studio you need to use ITestOutputHelper, but there is no facility in place to pass that in. When its passed in by the constructor and injected as xUnit wants it to be it gets assigned, but when the class is recreated by the Host I end up with a mocked version of ITestOutputHelper. I've used logging to work around this, but log messages don't show in Visual Studio.

mwhelan commented 8 years ago

Hi @DamianReeves. BDDfy and Specify are test framework agnostic, and that was great when both output to the console. However, with xUnit 2 (and NUnit 3) BDDfy's console reporter no longer works, and this sort of issue you raise here requires test framework-specific solutions. I am tempted to create a Specify.xUnit package and a Specify.NUnit package...

However, the current design of Specify lets you create your own ScenarioFor base class, which I see as a great opportunity to add project-specific helpers that you want to make available to every test in a particular test project. Apologies that this is a bit hacky. I can see how this could be improved in the Specify framework itself and I am open to suggestions about improvements you might like to see.

public abstract class ScenarioFor<TSut> : Specify.ScenarioFor<TSut>
        where TSut : class
{
    protected static ITestOutputHelper Console;

    protected ScenarioFor(ITestOutputHelper testOutputHelper)
    {
        if (testOutputHelper.GetType() == typeof(TestOutputHelper))
        {
            Console = testOutputHelper;
        }
    }

    [Fact]
    public override void Specify()
    {
        base.Specify();
    }
}

As you correctly pointed out, the class is being created twice, once by xUnit (where the ITestOutputHelper is provided by xUnit) and then by Specify, which is providing the mocked version. Ideally I would tell the Specify Container how to resolve this from xUnit in the project setup, then it would provide that version rather than the mock for every test, but I don't know how to do that with xUnit.

This now correctly outputs to the console using the Console property on the base class:

public class MyTestClass : ScenarioFor<Calculator>
{
    private int _result;

    public MyTestClass(ITestOutputHelper testOutputHelper)
       : base(testOutputHelper)
    {
    }

    public void When_I_add_two_and_three()
    {
        Console.WriteLine("Adding numbers {0} and {1}", 2, 3);
        _result = SUT.Add(2, 3);
    }

    public void Then_the_result_is_five()
    {
        _result.ShouldBe(5);
    }
}
DamianReeves commented 8 years ago

I actually had considered this very solution but I was unsure about how xUnit works internally. It turns out this does work. Thanks.

mwhelan commented 8 years ago

It's a bit more hacky than I would like. I would like to learn a bit more about how xunit works but unfortunately I'm a bit starved for time at the moment as I've just started a new job. Any feedback is welcome though, so please do let me know if you come across anything. Thanks.