Extract Embedded Resources With An Attribute In MbUnit

UPDATE: This functionality is now rolled into the latest version of MbUnit.

A long time ago Patrick Cauldwell wrote up a technique for managing external files within unit tests by embedding them as resources and unpacking the resources during the unit test. This is a powerful technique for making unit tests self contained.

If you look in our unit tests for Subtext, I took this approach to heart, writing several different methods in our UnitTestHelper class for extracting embedded resources.

Last night, I had the idea to make the code cleaner and even easier to use by implementing a custom test decorator attribute for my favorite unit testing framework, MbUnit.

Usage Examples

The following code snippets demonstrates the usage of the attribute within a unit test. These code samples assume an embedded resource already exists in the same assembly that the unit test itself is defined in.

This first test demonstrates how to extract the resource to a specific file. You can specify a full destination path, or a path relative to the current directory.

[Test]
[ExtractResource("Embedded.Resource.Name.txt", "TestResource.txt")]
public void CanExtractResourceToFile()
{
  Assert.IsTrue(File.Exists("TestResource.txt"));
}

The next demonstrates how to extract the resource to a stream rather than a file.

[Test]
[ExtractResource("Embedded.Resource.Name.txt")]
public void CanExtractResourceToStream()
{
  Stream stream = ExtractResourceAttribute.Stream;
  Assert.IsNotNull(stream, "The Stream is null");
  using(StreamReader reader = new StreamReader(stream))
  {
    Assert.AreEqual("Hello World!", reader.ReadToEnd());
  }
}

As demonstrated in the previous example, you can access the stream via the static ExtractResourceAttribute.Stream property. This is only set if you don’t specify a destination.

In case you’re wondering, the stream is stored in a static member marked with the [ThreadStatic] attribute. That way if you are taking advantage of MbUnits ability to repeat a test multiple times using multiple threads, you should be OK.

What if the resource is embedded in another assembly other than the one you are testing?

Not to worry. You can specify a type (any type) defined in the assembly that contains the embedded resource like so:

[Test]
[ExtractResource("Embedded.Resource.txt"
  , "TestResource.txt"
  , ResourceCleanup.DeleteAfterTest
  , typeof(TypeInAssemblyWithResource))]
public void CanExtractResource()
{
  Assert.IsTrue(File.Exists("TestResource.txt"));
}

[Test]
[ExtractResource("Embedded.Resource.txt"
  , typeof(TypeInAssemblyWithResource))]
public void CanExtractResourceToStream()
{
  Stream stream = ExtractResourceAttribute.Stream;
  Assert.IsNotNull(stream, "The Stream is null");
  using (StreamReader reader = new StreamReader(stream))
  {
    Assert.AreEqual("Hello World!", reader.ReadToEnd());
  }
}

This attribute should go a long way to making unit tests that use external files cleaner. It also demonstrates how easy it is to extend MbUnit.

A big Thank You goes to Jay Flowers for his help with this code. And before I forget, you can download the code for this custom test decorator here.

Please note that I left in my unit tests for the attribute which will fail unless you change the embedded resource name to match an embedded resource in your own assembly.

Technorati tags: , ,

What others have said

Requesting Gravatar... DotNetKicks.com Apr 27, 2007 11:46 AM
# Extract Embedded Resources With An Attribute In MbUnit
You've been kicked (a good thing) - Trackback from DotNetKicks.com
Requesting Gravatar... Patrick Cauldwell Apr 27, 2007 12:22 PM
# re: Extract Embedded Resources With An Attribute In MbUnit
That's awesome, Phil! I can't wait to try it out.
Requesting Gravatar... Jonathan de Halleux Apr 28, 2007 9:39 AM
# re: Extract Embedded Resources With An Attribute In MbUnit
Pretty cool indeed.
Requesting Gravatar... Andrew Stopford's Weblog May 10, 2007 2:25 PM
# MbUnit tips #2
Phil Haack has two great posts. A new fixture for embeding resources in tests , Phil has contributed
Requesting Gravatar... WPF Community Bloggers May 11, 2007 5:55 AM
# MbUnit tips #2
Phil Haack has two great posts. A new fixture for embeding resources in tests , Phil has contributed
Requesting Gravatar... Rob Cecil May 17, 2007 2:21 PM
# re: Extract Embedded Resources With An Attribute In MbUnit
I would suggest some small changes to your ExtractResourceAttribute.cs

1. Decorating the ExtractResourceRunInvoker.Execute() with [System.Diagnostics.DebuggerNonUserCodeAttribute( )]
(and others that may benefit)

2. Change Execute() to:

public override object Execute( object o, IList args )
{
Assembly assembly = attribute.Type.Assembly;

using ( Stream stream = assembly.GetManifestResourceStream( attribute.ResourceName ) )
{
try
{
if ( String.IsNullOrEmpty( attribute.Destination ) )
{
ExtractResourceAttribute.stream = stream;
return this.Invoker.Execute( o, args );
}
else
{
WriteResourceToFile( stream );
}
}
catch ( TargetInvocationException tie )
{
if ( tie.InnerException != null )
{
throw new Exception( tie.InnerException.Message, tie.InnerException );
}
throw;
}
}

try
{
return this.Invoker.Execute( o, args );
}
catch ( TargetInvocationException tie )
{
if ( tie.InnerException != null )
{
throw new Exception( tie.InnerException.Message, tie.InnerException );
}
throw;
}
finally
{
if ( attribute.ResourceCleanup == ResourceCleanup.DeleteAfterTest )
File.Delete( attribute.Destination );
}
}

Which means I never have to see TargetInvocationException unless there isn't a useful InnerException.
Requesting Gravatar... Rob Cecil May 17, 2007 2:34 PM
# re: Extract Embedded Resources With An Attribute In MbUnit
I would also make this change, otherwise, WriteResourceToFile( Stream ) below blows up with a null stream.

[System.Diagnostics.DebuggerNonUserCodeAttribute( )]
public override object Execute( object o, IList args )
{
Assembly assembly = attribute.Type.Assembly;

if ( !new List<string>( assembly.GetManifestResourceNames( ) ).Contains( attribute.ResourceName ) )
{
throw new Exception( String.Format( "Unable to find embedded resource '{0}' in assembly '{1}'.",
attribute.ResourceName, assembly.FullName ) );
}
Requesting Gravatar... Haacked May 18, 2007 8:30 AM
# re: Extract Embedded Resources With An Attribute In MbUnit
Hi Rob, thanks for the suggestions! I'll try and make sure they get into the next build.
Requesting Gravatar... Rob Cecil May 21, 2007 9:52 AM
# re: Extract Embedded Resources With An Attribute In MbUnit
One more change, Phil. This time allowing for the case where the resource might be organized into subfolders in the assembly. The .net assembly as you know converts subfolders into dots, so 'Results\test_results.xml' becomes '<typename>.results.testresults.xml'. Here is the Execute() method again, with changes:



[System.Diagnostics.DebuggerNonUserCodeAttribute]
public override object Execute( object o, IList args )
{
Assembly assembly = attribute.Type.Assembly;

string resourceName = assembly.GetName( ).Name + "." + attribute.ResourceName.Replace( '/', '.' );

if ( !new List<string>( assembly.GetManifestResourceNames( ) ).Contains( resourceName ) )
{
throw new Exception( String.Format( "Unable to find embedded resource '{0}' in assembly '{1}'.",
attribute.ResourceName, assembly.FullName ) );
}
using ( Stream stream = assembly.GetManifestResourceStream( resourceName ) )
{

try
{
if ( String.IsNullOrEmpty( attribute.Destination ) )
{
ExtractResourceAttribute.stream = stream;
return this.Invoker.Execute( o, args );
}
else
{
WriteResourceToFile( stream );
}
}
catch ( TargetInvocationException tie )
{
if ( tie.InnerException != null )
{
throw new Exception( tie.InnerException.Message, tie.InnerException );
}
throw;
}
}

try
{
return this.Invoker.Execute( o, args );
}
catch ( TargetInvocationException tie )
{
if ( tie.InnerException != null )
{
throw new Exception( tie.InnerException.Message, tie.InnerException );
}
throw;
}
finally
{
if ( attribute.ResourceCleanup == ResourceCleanup.DeleteAfterTest )
File.Delete( attribute.Destination );
}
}
Requesting Gravatar... Haacked May 21, 2007 2:01 PM
# re: Extract Embedded Resources With An Attribute In MbUnit
Hmmm... I'd almost rather require the tester to give the full resource name always. I use Reflector to find that out.
Requesting Gravatar... Haacked May 21, 2007 2:02 PM
# re: Extract Embedded Resources With An Attribute In MbUnit
That avoids the issue of duplicate resources where the resource file name is the same, but the paths are different and the wrong one gets selected.

Then again, maybe that's such a small risk as to not be concerned.
Requesting Gravatar... JIRA: MbUnit May 22, 2007 4:17 PM
# [MBUNIT-118] Extract Embedded Resource Attribute
Contributed by Phil Haack<br /><br />http://haacked.com/archive/2007/04/27/extract-embedded-resources-with-an-attribute-in-mbunit.aspx null
Requesting Gravatar... you've been HAACKED May 25, 2007 8:50 AM
# Motivate Your Unit Tests With the Release of MbUnit 2.4
Motivate Your Unit Tests With the Release of MbUnit 2.4
Requesting Gravatar... Community Blogs May 25, 2007 9:23 AM
# Motivate Your Unit Tests With the Release of MbUnit 2.4
Are your unit tests a little flat lately? Have they lost their shine and seem a bit directionless? Maybe
Requesting Gravatar... Paul Schofield Jun 06, 2008 10:57 AM
# re: Extract Embedded Resources With An Attribute In MbUnit
Has anyone gotten this working with MbUnit v3?

What do you have to say?

(will show your gravatar)
Please add 3 and 1 and type the answer here: