ExpandoObject Explained In Tests (Except For One Mystery)

Monday, November 8, 2010

Update: Thanks to James and Samuel.  I’ve wrongly assumed that every test runner in the world re-instantiates the test fixture before executing each test method – but some do and some don’t. In this case I was using NUnit and the “mystery” is solved by adding an NUnit [Setup] method to create a fresh, new expando for each test run. Seems obvious, but only after ejecting all assumptions.

xUnit.net and the Visual Studio mstest tools DO create a fresh instance of the test fixture for each test method they execute inside the fixture. NUnit and MBUnit only create the fixture once, and as Jim Newkirk says:

I think one of the biggest screw-ups that was made when we wrote NUnit V2.0 was to not create a new instance of the test fixture class for each contained test method. I say "we" but I think this one was my fault. I did not quite understand the reasoning in JUnit for creating a new instance of the test fixture for each test method. I look back now and see that reusing the instance for each test method allows someone to store a member variable from one test and use it another. This can introduce execution order dependencies which for this type of testing is an anti-pattern.

The anti-pattern is demonstrated below. Smile

 

The following tests all pass for .NET 4.0’s ExpandoObject.

private dynamic expando = new ExpandoObject();

[Test]
public void Can_Add_A_Member()
{
    var expected = "Scott";            
    expando.Name = expected;
    
    Assert.AreEqual(expected, expando.Name);
}

[Test]
public void But_Cannot_Reflect_It()
{
    expando.Name = "Scott";

    Assert.IsNull(expando.GetType().GetProperty("Name"));           
}

[Test]
public void Is_A_Dictionary()
{
    var expected = "Scott";
    var dictionary = expando as IDictionary<string, object>;
    expando.Name = expected;

    Assert.AreEqual(expected, dictionary["Name"]);
}

[Test]
public void And_Also_Enumerable()
{
    var enumerable = expando 
             as IEnumerable<KeyValuePair<string, object>>;
    expando.Name = "Scott";

    Assert.IsTrue(enumerable.Any(kv => kv.Key == "Name"));
}

What’s The Mystery?

ExpandoObject implements INotifyPropertyChanged and the following test will pass.

[Test]
public void Will_Raise_PropertyChangedEvent()
{            
    dynamic expando = new ExpandoObject();
    var propertyName = "";
    ((INotifyPropertyChanged)expando).PropertyChanged +=
        (sender, args) => 
        {               
            propertyName = args.PropertyName;
        };
    expando.Name = "Scott";
    
    Assert.AreEqual("Name", propertyName);
}   

But, this version of the test fails unless you run under the debugger.

private dynamic expando = new ExpandoObject();        

[Test]
public void Will_Raise_PropertyChangedEvent()
{                        
    var propertyName = "";
    ((INotifyPropertyChanged)expando).PropertyChanged +=
        (sender, args) => 
        {               
            propertyName = args.PropertyName;
        };
    expando.Name = "Scott";
    
    Assert.AreEqual("Name", propertyName);
}   

In the passing test expando is local variable. In the failing test expando is a field. Scary.


Comments
gravatar David Starr Monday, November 8, 2010
What a great way to explain something. This worked perfectly for me.
gravatar James Kovacs Monday, November 8, 2010
I tried running your failing test both with and without the debugger (via the ReSharper Test Runner) and it worked both ways. Not sure why it's failing for you, but it is worrying.
gravatar scott Monday, November 8, 2010
Hmm. If you get a chance - try this project: odetocode.com/downloads/classlibrary2.zip

I've had it fail both with the R# test runner and the NUnit test runner on two different machines.

I wonder what the difference could be.
gravatar Scott Allen Monday, November 8, 2010
Holy cow - if I delete all the other tests the failing test will pass!
gravatar Samuel Jack Tuesday, November 9, 2010
Scott,
There's a straightforward explanation: Reflector it, and you'll see that ExpandoObject only raises PropertyChanged events when the value actually changes, not when you re-assign the same value as before.

Since your other tests are using the same ExpandoObject instance, and assigning the same value of "Scott" to the Name property, no event is being raised. Change the failing test to use a different property, or give it a different value, and it all works!
gravatar Samuel Jack Tuesday, November 9, 2010
I should also note that if I use MbUnit as the test framework, the original version fails whether I use the debugger or not.

I guess it all depends on whether your test framework creates a new instance of the Test Fixture class before running each test.
gravatar scott Tuesday, November 9, 2010
@SJ - Right - but the test runner is supposed to re-instantiate the test fixture for each run, so the property is always changing.
gravatar scott Tuesday, November 9, 2010
OK - so test runners don't instantiate the fixture once per test method. That explains the mystery. I'm shocked I haven't run into this before.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!