Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extend xUnit.NET to use custom code when processing a class and locating test methods

I'm a big fan of the xUnit.NET framework; I find it light, simple, clean, and extensible.

Now let's say that I have a class like so:

public class AdditionSpecification
{
  static int result;

  public void Because()
  {
    result = 2 + 2;
  }

  public void Result_is_non_zero()
  {
    Assert.True(result <> 0);
  }

  public void Result_is_correct()
  {
    Assert.Equal(4, result);
  }
}

With the test class above I want xUnit.NET to see 2 test cases and to run the Because() method before each of them.

Leaving aside any issues you may have with my class or method names, the structure of this test/specification, the xUnit.NET framework, or BDD, here's my question:

How can I tell xUnit.NET that I want to customize how it identifies and executes test methods out of this class without using a custom [Fact]-like attribute on each target test method?

I know that I can derive from BeforeAfterAttribute to decorate each test method with custom before and after execution. How can i do this at the class level? Do i have to write a custom runner?

like image 436
David Alpert Avatar asked Jan 28 '09 15:01

David Alpert


2 Answers

xUnit.net's IUseFixture allows you to do per fixture setup. You could therefore define your own fixture class:

public class AdditionFixture : IDisposable
{
  public int Because() 
  {
    return 2 + 2;
  }

  public void Dispose()
  {
   //test tear down code
  }      
}

Your test class can then implement this (with setFixture requiring implementing) :

public class AdditionSpecification : IUseFixture<AdditionFixture>
{
  int result;

  public void SetFixture(AdditionFixture Fixture)
  {
   result = Fixture.Because();
  }

  [Fact]
  public void Result_is_non_zero()
  {
   Assert.True(result <> 0);
  }

  [Fact]
  public void Result_is_correct()
  {
   Assert.Equal(4, result);
  }
}

The xUnit runner will create a single instance of your fixture, and pass it into SetFixture before running each test. After running all of your tests, the runner will then dispose of the fixture if it implements IDisposable. I hope that helps!

The xUnit wiki on codeplex has more information, including a nice example of how to implement IUseFixture to manage a database connection for you test fixtures.

like image 187
BenA Avatar answered Sep 23 '22 13:09

BenA


So it turns out that I was looking for the ITestClassCommand.EnumerateTestMethods() method.

  1. The default xUnit.NET test runner will iterate over all the classes in your test assembly.
  2. For each one it will check for a RunWithAttribute; that's your chance to override the ITestClassCommand implementation that is used to identify methods containing tests. (RunWithNUnit is a good example)
  3. ITestClassCommand.EnumerateTestMethods() is called to process the test class and return an IEnumerable of test methods.
  4. each test IMethodInfo is then passed to ITestClassCommand.EnumerateTestCommands(IMethodInfo testMethod) to get the IEnumerable of ITestCommands
  5. each ITestCommand is then executed and given the opportunity to return a result.

In the case of my example above, I would need something like:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class RunWithMyTestClassCommandAttribute : RunWithAttribute
{
   public RunWithMyTestClassCommandAttribute()
               : base(typeof(MyTestClassCommand)) {}
}

Then I could decorate my above example with:

[RunWithMyTestClassCommand]
public class AdditionSpecification
{
  static int result;

  public void Because()
  {
    result = 2 + 2;
  }

  public void Result_is_non_zero()
  {
    Assert.True(result <> 0);
  }

  public void Result_is_correct()
  {
    Assert.Equal(4, result);
  }
}

Finally, in MyTestClassCommand, I get to opportunity between EnumerateTestMethods() and EnumerateTestCommands(IMethodInfo testMethod) to use whatever logic I want to locate and construct ITestCommand instances that get executed as individual tests.

BTW, in the process of researching this issue, I ran into a small bug in the xUnit.NET framework where a custom IMethodInfo generated by EnumerateTestMethods() never showed up in EnumerateTestCommands(..) because it was being unwrapped and rewrapped by the test runner or one of it's factories.

I submitted this issue to the xUnit project on codeplex and it was corrected on May 30th, 2009 for xUnit.NET 1.5 CTP 2

like image 27
David Alpert Avatar answered Sep 23 '22 13:09

David Alpert