Why is this inherited Establish executed multiple times?

333 Views Asked by At

My understand is that each Establish should only be executed once, but the code below shows it executing multiple times. We're nesting the classes to provide some grouping while keeping the unit tests for a Subject in one file. This seems like it is a bug.

We're using the machine.specifications.runner.resharper Reshaper extension and MSpec 0.9.1.

[Subject(typeof(string))]
internal class EstablishRunTwice {
    Establish sharedContext = () => Console.WriteLine("Shared context");

    internal class ScenarioA : EstablishRunTwice {
        Establish scenarioAContext = () => Console.WriteLine("ScenarioA context");

        internal class ScenarioAVariation1 : ScenarioA {
            Because of = () => Console.WriteLine("ScenarioAVariation1 Because");

            It it1 = () => Console.WriteLine("ScenarioAVariation1 It1");

            It it2 = () => Console.WriteLine("ScenarioAVariation1 It2");
        }

        internal class ScenarioAVariation2 : ScenarioA {
            Because of = () => Console.WriteLine("ScenarioAVariation2 Because");

            It it1 = () => Console.WriteLine("ScenarioAVariation2 It1");

            It it2 = () => Console.WriteLine("ScenarioAVariation2 It2");
        }
    }

    internal class ScenarioB : EstablishRunTwice {
        Establish context = () => Console.WriteLine("ScenarioB context");

        Because of = () => Console.WriteLine("ScenarioB Because");

        It it1 = () => Console.WriteLine("ScenarioB It1");

        It it2 = () => Console.WriteLine("ScenarioB It2");
    }
}

The result is this for ScenarioAVariation1:

Shared context
Shared context
ScenarioA context
Shared context
Shared context
ScenarioA context
ScenarioAVariation1 Because
ScenarioAVariation1 It1
ScenarioAVariation1 It2

When we were doing our own custom context specification framework using NUnit, we got around issues with the NUnit running by making sure all subclasses were abstract (in this case, EstablishRunTwice and ScenarioA would be abstract), but MSpec throws an error attempting to do so.

2

There are 2 best solutions below

6
On

That's a really confusing way to structure things - clever, but perhaps a bit too clever. I find it hard to read and understand the intent. In fact, I can't even begin to imagine what the compiler would do with that inheritance structure and therefore I can't understand the intent. I think maybe you're over-thinking this.

So let me see, ScenarioA is not only nested in EstablishRunTwice, but also inherits from it. Does that mean it inherits nested copies of itself all the way down to infinity? And then, ScenarioB inherits from all of that! My head has just exploded. I'm not surprised you get confusing results. What does that nesting really give you? Does it make the code more readable or easier to maintain? I'm not convinced it does.

Use the KISS principle. The normal way of doing things is to put each context in its own class, no nesting; just use files to group related tests, and you can also use the Concern argument in the [Subject] attribute as another way of grouping. You can inherit from other contexts if that makes sense, but after working with MSpec for a couple of years, I'm slowly coming to the conclusion that too much inheritance can harm readability and make the test code more viscous, so use inheritance wisely.

Update: Having reflected on what I think you are trying to achieve for a bit longer, I suspect you are trying to re-invent behaviours. This is perhaps a poorly documented and understood feature of MSpec, which lets you define a set of common behaviours that can later be applied in multiple test contexts. Does that sound like what you are trying to achieve? Here's an example of behaviours:

[Behaviors]
internal class DenebRightAscension
    {
    It should_have_20_hours_ = () => UUT.Degrees.ShouldEqual(20u);
    It should_have_41_minutes = () => UUT.Minutes.ShouldEqual(41u);
    It should_have_59_seconds = () => UUT.Seconds.ShouldEqual(59u);
    protected static Bearing UUT;
    }


[Subject(typeof(HourAngle), "sexagesimal")]
internal class when_converting_hour_angle_to_sexagesimal
{
    Because of = () =>
    {
        RaDeneb = 20.6999491773451;
        UUT = new Bearing(RaDeneb);
    };

    Behaves_like<DenebRightAscension> deneb;

    protected static Bearing UUT;
    static double RaDeneb;
}

[Subject(typeof(Bearing), "sexagesimal")]
internal class when_converting_to_sexagesimal
    {
    Because of = () =>
        {
        RaDeneb = 20.6999491773451;
        UUT = new Bearing(RaDeneb);
        };

    Behaves_like<DenebRightAscension> deneb;

    protected static Bearing UUT;
    static double RaDeneb;
    }

Note that in behaviours fields are matched by name, not by any kind of inheritance. So the behaviour magically knows what I mean by 'UUT' even though the classes are in no way related.

0
On

It is because you are both nesting and inheriting the test classes. Normally you might use nested classes in C# purely for organizational purposes, but it also has an effect on execution in MSpec. This might be unexpected, but does fit with its declarative style. In fact, there isn't normally a need to use inheritance at all for MSpec unless you're reusing functionality across different files.

Just remove the inheritance in your example and keep the nesting and you'll see the output as:

Shared context
ScenarioA context
ScenarioAVariation1 Because
ScenarioAVariation1 It1
ScenarioAVariation1 It2
...

This makes it easy to use common setup in the Establish of the outer class and override specific parts in inner classes. On a personal note, before I realized it worked this way, I felt like I was fighting with MSpec for test cases that relied on different setups (vs ones where different values are passed directly to the Subject in the Because).

Say you had a weather sensor thingy, you might structure it this way:

[Subject(typeof(WeatherSensor))]
class when_reading_the_sensor : WithSubject<WeatherSensor> {
  Establish context = () => { common setup }

  class with_sunny_conditions {
    Establish context = () => { setup sunny conditions }

    Because of = () => Subject.Read();

    It should_say_it_is_sunny => () => ...
    It should_return_correct_temps => () => ...
  }

  class with_rainy_conditions {
    ...
  }
}

That also reads well in the test results. Given the second test fails, it might show like this in the test tree:

  • (X) WeatherSensor, when reading the sensor with sunny conditions
    • (✔) should say it is sunny
    • (X) should return correct temps

If, as in that example, all the different conditions come purely from setup of dependencies injected into the Subject, you may even want to move the Because into the outer class. Then you can just have an Establish and some Its in the inner classes, making each test case very concise. The outer Because will still be run for each inner class after all the needed Establishes and before the Its.