Dashboard > MbUnit > ... > MbUnit Home > ExtendingMbUnitWithYourTestDecorator
  MbUnit Log In View a printable version of the current page.  
  ExtendingMbUnitWithYourTestDecorator
Added by Jonathan de Halleux, last edited by Jonathan de Halleux on Jul 02, 2005
Labels: 
(None)

MbUnit supports a system of [DynamicProxy] that can be attached to tests. Such 'strange thing' is also called a TestDecorator because it "decorates" the way the test is executed. The most famous example of test decorated is ExpectedExceptionAttribute which verifies that a method throws the right exception.
Different problems, different needs, therefore TestDecorator in MbUnit have been designed to be extended in an easy fashion.

Example

Let us illustrate this with an example. Consider a decorator that sets an environment variable value and cleans up once the test is done. So, for example, we would like to write tests like this:

Unable to find source-code formatter for language: cs. Available languages are: actionscript, html, java, javascript, none, sql, xhtml, xml
[TestFixture] 
public class MyFixture
{
    [Test]
    [PushEnvironmentVariable("Path","")]
    public void RunTestWithoutPath()
    { ... }
}

In this example, we expect that the Path variable is set to "" for the test and restore afterwards.

Creating a new decorator

Creating a new decorator involves 2 steps:

  1. Inherit a new attribute from the abstract base attribute class DecoratorPatternAttribute
  2. Inherit a new IRunInvoker from the abstract base class DecoratorInvokerAttribute. Usually, this class can be implemented as a private class inside the attribute.

When MbUnit loads the tests, it looks for any [DecoratePatternAttribute]. This class defines an abstract method that installs the dynamic proxy:

Unable to find source-code formatter for language: cs. Available languages are: actionscript, html, java, javascript, none, sql, xhtml, xml
public abstract IRunInvoker GetDecorator(IRunInvoker invoker);

[IRunInvoker] is an interface that defines an executable instance. For instance, it can be a wrapper around a MethodInfo invokation. This interface defines an Execute method that is invoked by MbUnit:

Unable to find source-code formatter for language: cs. Available languages are: actionscript, html, java, javascript, none, sql, xhtml, xml
public Object Execute(Object fixture, IList args);

The GetDecorator method is usually quite short because we just install the proxy (step 2) which contains the real interresting code. The skeleton of the application looks like this:

Unable to find source-code formatter for language: cs. Available languages are: actionscript, html, java, javascript, none, sql, xhtml, xml
namespace MbUnit.Framework
{
   [AttributeUsage(AttributeTargets.Method, AllowMultiple  = true, Inherited =true)]    
    public sealed class PushEnvironmentVariableAttribute : DecoratorPatternAttribute
    {
       ...

       public override IRunInvoker GetInvoker(IRunInvoker invoker)
       {
           return new PushEnvironmentVariableRunInvoker(invoker, this);
       }

       private sealed class PushEnvironmentVariableRunInvoker : DecoratorRunInvoker
       {
           private PushEnvironmentVariableAttribute attribute;
           public PushEnvironmentVariableRunInvoker(
               IRunInvoker invoker,
               PushEnvironmentVariableAttribute attribute
               )
               :base(invoker)
           {
               this.attribute = attribute;
           }

           public override Object Execute(Object o, IList args)
           {
               // pre actions
               ...
               // invoke wrapped invoker
               this.Invoker.Execute(o,args);
               // post actions
               ...
           }
       }
   }
}

Of course, we still have to write a lot of gaps but the rest is detail (full implementation is given below).

Example source

Unable to find source-code formatter for language: cs. Available languages are: actionscript, html, java, javascript, none, sql, xhtml, xml
using System;
using System.Collections;
using MbUnit.Core.Framework;
using MbUnit.Core.Invokers; 

namespace MbUnit.Framework
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple  = true, Inherited =true)]
    public sealed class PushEnvironmentVariableAttribute : DecoratorPatternAttribute
    {
        private string name;
        private string value; 

        public PushEnvironmentVariableAttribute(
            string name,
            string value
             )
        {
            this.name = name;
            this.value = value;
        }

        public string Name
        {
             get { return this.name; }
        }

        public string Value
        {
            get { return this.value; }
        }
 
        public override IRunInvoker GetInvoker(IRunInvoker invoker)
        {
            return new PushEnvironmentVariableRunInvoker(invoker, this);
        } 

        private sealed class PushEnvironmentVariableRunInvoker :
            DecoratorRunInvoker
        {
            private PushEnvironmentVariableAttribute attribute;
            public PushEnvironmentVariableRunInvoker(
                IRunInvoker invoker,
                PushEnvironmentVariableAttribute attribute
                )
                :base(invoker)
            {
                this.attribute = attribute;
            } 

            public override Object Execute(Object o, IList args)
            {
               // store previous value
               string previousValue = Environment.GetEnvironmentVariable(
                   attribute.Name
                   );  
               try
               {
                   // install new value
                   Environment.SetEnvironmentVariable(
                       attribute.Name,
                       attribute.Value
                       );  

                   // run base invoker
                   return this.Invoker.Execute(o, args);
               }
               finally
               {
                   // cleaning our mess
                   Environment.SetEnvironmentVariable(
                       attribute.Name,
                       previousValue
                       );
               }
           }
       }
   }
}

Site powered by a free Open Source Project / Non-profit License (more) of Confluence - the Enterprise wiki.
Learn more or evaluate Confluence for your organisation.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.2.9 Build:#527 Sep 07, 2006) - Bug/feature request - Contact Administrators