Testing async methods with MbUnit

XUnit 1.9 recently shipped, featuring support for async tests:

[Test]
public async Task MyAsyncUnitTest()
{
    // ... setup code here ...

    var result = await CallMyAsyncApi(...);

    // ... assertions here ...
}

Here’s how that looks using MbUnit:

public class AsyncTestAttribute : TestAttribute
{
	/// <inheritdoc />
	protected override void Execute(PatternTestInstanceState state)
	{
		var result = state.InvokeTestMethod();

		var task = result as Task;
		if (task != null)
			task.Wait();
	}
}

Sweet! This’ll be in the next MbUnit/Gallio release (but only if you also reference MbUnit40.dll).

Using NHamcrest with NUnit or XUnit

NHamcrest is supported out of the box with MbUnit, but there’s nothing stopping you using it with any other framework.

You just need to write the shim code, i.e. Assert.That.

For example, with NUnit it would look something like this:

public static void That<T>(T actual, IMatcher<T> matcher, string message, params object[] args)
{
	if (matcher.Matches(actual))
		return;
	
	var writer = new TextMessageWriter(message, args);

	WriteExpected(matcher, writer);

	WriteActual(actual, matcher, writer);

	throw new AssertionException(writer.ToString());
}

private static void WriteExpected(ISelfDescribing matcher, TextWriter writer)
{
	writer.Write(TextMessageWriter.Pfx_Expected);
	var description = new StringDescription();
	matcher.DescribeTo(description);
	writer.Write(description.ToString());
	writer.WriteLine();
}

private static void WriteActual<T>(T actual, IMatcher<T> matcher, TextWriter writer)
{
	writer.Write("  But ");
	var mismatchDescription = new StringDescription();
	matcher.DescribeMismatch(actual, mismatchDescription);
	writer.Write(mismatchDescription.ToString());
	writer.WriteLine();
}

For XUnit:

public static void That<T>(T actual, IMatcher<T> matcher)
{
	if (matcher.Matches(actual))
		return;

	var description = new StringDescription();
	matcher.DescribeTo(description);

	var mismatchDescription = new StringDescription();
	matcher.DescribeMismatch(actual, mismatchDescription);

	throw new MatchException(description.ToString(), mismatchDescription.ToString(), null);
}

public class MatchException : AssertActualExpectedException
{
	public MatchException(object expected, object actual, string userMessage) : base(expected, actual, userMessage)
	{
	}
}

And, for completeness, here’s the MbUnit version:

        public static void That<T>(T item, IMatcher<T> matcher, string messageFormat, params object[] messageArgs)
        {
            AssertionHelper.Verify(() =>
            {
                if (matcher.Matches(item))
                    return null;

                var description = new StringDescription();
                var mismatchDescription = new StringDescription();

                matcher.DescribeTo(description);
                matcher.DescribeMismatch(item, mismatchDescription);

                return new AssertionFailureBuilder("Expected " + description)
                    .SetMessage(messageFormat, messageArgs)
                    .AddLabeledValue("Expected", description.ToString())
                    .AddLabeledValue("But", mismatchDescription.ToString())
                    .ToAssertionFailure();
            });           
        }

External Annotations for ReSharper

Most ReSharper information comes from code inspection and analysis, but not all details can be inferred from the source code alone. For example, code called by reflection (e.g. Activator.CreateInstance) looks like dead code to static analysis. R# offers a solution in the form of External Annotations, XML files supplying extra information.

If you use a test framework other than NUnit, e.g. MbUnit, you may find that your tests are marked as unused by R# even though they can be run. This can fixed by supplying some annotations, the XML needs to go in “%SystemDrive%\Program Files\JetBrains\ReSharper\[Build#]\Bin\ExternalAnnotations\[DllName]\[DllName].xml”. So for MbUnit, it would be ExternalAnnotations\MbUnit\MbUnit.xml. The file needs a list of types, associated with the appropriate R# attribute. The MeansImplicitUseAttribute:

<assembly name="MbUnit">
  <member name="T:MbUnit.Framework.TestAttribute">
    <attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor" />
  </member>

states that any code tagged with the TestAttribute will not be considered unused. Other useful attributes are the AssertionMethodAttribute:

  <member name="M:MbUnit.Framework.Assert.IsNotNull(System.Object)">
    <attribute ctor="M:JetBrains.Annotations.AssertionMethodAttribute.#ctor"/>
    <parameter name="anObject">
      <attribute ctor="M:JetBrains.Annotations.AssertionConditionAttribute.#ctor(JetBrains.Annotations.AssertionConditionType)">
        <argument>3</argument>
      </attribute>
    </parameter>
  </member>

the TerminatesProgramAttribute:

  <member name="M:MbUnit.Framework.Assert.Fail">
    <attribute ctor="M:JetBrains.Annotations.TerminatesProgramAttribute.#ctor"/>
  </member>

and the NotNullAttribute. An external annotation file is shipped with the more recent versions of MbUnit.