The pattern utilises object inheritance to make it powerful and descriptive. To illustrate, we must start with an example. The code below contains a method that tests two conditions and tries to return an object. If both conditions fail then the method throws an exception. It is worth noting that the sample class is not a good example of OO design, but rather a simplified case. The important point is that there is a path through the method that will cause an exception to be thrown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ScoreChecker | |
{ | |
public IPlayer GetScoredPlayer(IPlayer player) | |
{ | |
if (player.Score > 8) | |
{ | |
return new HighScorePlayer(player); | |
} | |
if (player.Score > 2) | |
{ | |
return new LowScorePlayer(player); | |
} | |
throw new ScoreTooLowException(); | |
} | |
} |
This means we don't have to repeat this call to the same target method in any of the classes that inherit from this base class. It makes the classes easy to navigate and the test output becomes straight forward as coherent sentences are produced: "When I Get Scored Player, Given The Player Has a Score Greater Than 8, Then the Result is a High Score Player". This is useful information when reviewing tests results.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public abstract class WhenIGetScoredPlayer : GivenA<ScoreChecker> | |
{ | |
protected readonly Mock<IPlayer> MockPlayer = new Mock<IPlayer>(); | |
protected IPlayer Result = null; | |
protected override void When() | |
{ | |
base.When(); | |
Result = Target.GetScoredPlayer(MockPlayer.Object); | |
} | |
} | |
public class GivenThePlayerHasAScoreGreaterThan8 : WhenIGetScoredPlayer | |
{ | |
protected override void Given() | |
{ | |
base.Given(); | |
MockPlayer.Setup(m => m.Score).Returns(9); | |
} | |
[Then] | |
public void TheResultIsAHighScorePlayer() | |
{ | |
Assert.IsInstanceOf<HighScorePlayer>(Result); | |
} | |
} | |
public class GivenThePlayerHasAScoreGreaterThan2 : WhenIGetScoredPlayer | |
{ | |
protected override void Given() | |
{ | |
base.Given(); | |
MockPlayer.Setup(m => m.Score).Returns(3); | |
} | |
[Then] | |
public void TheResultIsALowScorePlayer() | |
{ | |
Assert.IsInstanceOf<LowScorePlayer>(Result); | |
} | |
} |
I could start from scratch and perform all the setup in a new class. Making the call to GetScoredPlayer() inside of a [Test] method instead of as part of [SetUp]. But I don't want to duplicate unnecessarily. This is when I thought about the old pattern in a new way - using an override to postpone the call instead of starting all over again.
What I did was to still implement the base class with the appropriately named GivenThePlayerHasAScoreLowerThan2. The trick is to simply override the base When() and leave it empty. This prevents it from calling the GetScoredPlayer() during the [SetUp] phase of the test. It can now occur in the [Test] method, wrapped in an NUnit assertion that the correct type of Exception is thrown. Producing the desired output without unnecessary duplication: "When I Get Scored Player, Given The Player Has a Score Lower Than 2, A Score Too Low Exception Is Thrown".
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class GivenThePlayerHasAScoreLowerThan2 : WhenIGetScoredPlayer | |
{ | |
protected override void Given() | |
{ | |
base.Given(); | |
MockPlayer.Setup(m => m.Score).Returns(1); | |
} | |
protected override void When() | |
{ | |
// prevent base.When() from being called | |
// and throwing an exception too early! | |
} | |
[Then] | |
public void AScoreTooLowExceptionIsThrown() | |
{ | |
Assert.Throws<ScoreTooLowException>(() => base.When()); | |
} | |
} |