Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this inherited Establish executed multiple times?

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.

like image 647
Andy Avatar asked Nov 10 '22 07:11

Andy


1 Answers

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.

like image 86
ShawnFumo Avatar answered Nov 14 '22 23:11

ShawnFumo