ExpandoObject Explained In Tests (Except For One Mystery)

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.

Print | posted @ Monday, November 08, 2010 9:12 AM

Comments on this entry:

Gravatar # re: ExpandoObject Explained In Tests (Except For One Mystery)
by David Starr at 11/8/2010 9:26 AM

What a great way to explain something. This worked perfectly for me.
  
Gravatar # re: ExpandoObject Explained In Tests (Except For One Mystery)
by James Kovacs at 11/8/2010 10:24 PM

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 # re: ExpandoObject Explained In Tests (Except For One Mystery)
by scott at 11/8/2010 10:57 PM

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 # re: ExpandoObject Explained In Tests (Except For One Mystery)
by Scott Allen at 11/8/2010 11:07 PM

Holy cow - if I delete all the other tests the failing test will pass!
  
Gravatar # re: ExpandoObject Explained In Tests (Except For One Mystery)
by Samuel Jack at 11/9/2010 5:14 AM

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 # re: ExpandoObject Explained In Tests (Except For One Mystery)
by Samuel Jack at 11/9/2010 5:17 AM

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 # re: ExpandoObject Explained In Tests (Except For One Mystery)
by scott at 11/9/2010 7:46 AM

@SJ - Right - but the test runner is supposed to re-instantiate the test fixture for each run, so the property is always changing.
  
Gravatar # re: ExpandoObject Explained In Tests (Except For One Mystery)
by scott at 11/9/2010 7:51 AM

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 have been closed on this topic.
Scott Allen
Posts - 869
Comments - 4493
Stories - 14