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.
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")); }
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.