We started the migration to NUnit 3.0 as soon as the final version was released. Unfortunately, some bugs on Visual Studio Team Services ended this endeavour rather suddenly. We concentrated on shipping our product and postponed the upgrade. Now, with all our projects migrated, it is time to look back on the breaking changes that took the most time to upgrade.
The switch from NUnit 2.x to 3 was a big one. The team of NUnit did their best to inform about breaking changes, but we had other things to do than to check how a version we not yet can use expect us to write our code. That lead to even more code we needed to change later. This situation may be familiar for you and is the reason why a timely upgrade is the goal we should aim at.
If you hit a problem with your upgrade that isn’t covered here, you should check the list of breaking changes for a solution.
TestFixtureSetUp
There is no longer a TestFixtureSetUpAttribute. You could use this attribute to mark a setup method that would be executed prior to your tests:
1 2 3 4 5 |
[TestFixtureSetUp] public void SetUp() { _testee = new DebtCalculator(); } |
How often will this method be called? Once per test or once per fixture? I could never remember what it was. The new attribute name in NUnit 3 is clear:
1 2 3 4 5 |
[OneTimeSetUp] public void SetUp() { _testee = new DebtCalculator(); } |
ExpectedException
We used the ExpectedExceptionAttribute extensively to ensure that our code was called with the right values – or throw an exception otherwise. This attribute let you define what exception should be thrown and if this didn’t happen your test failed:
1 2 3 4 5 6 7 |
[Test] [ExpectedException(typeof(ArgumentOutOfRangeException))] public void Calculation_can_only_be_called_with_valid_CalculatorMethods() { var nonExistingMethod = (CalculatorMethod) 111; _testee.ByMethod(nonExistingMethod); } |
However, if you used a too generic exception or Exception itself, your code could fail for the wrong reason and still pass. NUnit 3 is much more explicit on which line should throw the exception:
1 2 3 4 5 6 7 8 |
[Test] public void Calculation_can_only_be_called_with_valid_CalculatorMethods() { var nonExistingMethod = (CalculatorMethod) 111; Assert.Throws<ArgumentOutOfRangeException>( () => _testee.ByMethod(nonExistingMethod)); } |
ExpectedException with a specific Message
If you had multiple places where the same exception could occur, you could specify what message you expected:
1 2 3 4 5 6 7 |
[Test] [ExpectedException(typeof(MyException), ExpectedMessage = "Invalid calulation method")] public void Extended_calculation_isnt_allowed() { _testee.ByMethod(CalculatorMethod.Extended); } |
In NUnit 3 you can not only specify what message you want, but also if you want the exact or a derived type of your exception:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[Test] public void Extended_calculation_isnt_allowed() { Assert.That(() => _testee.ByMethod(CalculatorMethod.Extended), Throws.TypeOf<MyException>() .With.Message.EqualTo("Invalid calulation method")); } [Test] public void Extended_calculation_isnt_allowed_CheckInstanceOf() { Assert.Throws( Is.InstanceOf(typeof(MyException)) .With.Message.StartWith("Invalid cal"), () => _testee.ByMethod(CalculatorMethod.Extended)); } |
CurrentDirectory
If you needed to reference the file system in your tests to load certain files or check that files where created, you could access them with a path relative to your bin folder:
1 2 3 4 5 6 7 8 9 10 11 |
[Test] public void Data_can_be_loaded_from_file_system() { var sourcePath = @".\TestData\2017.txt"; var targetPath = @".\TestResult\2017_calculated.txt"; var result = _testee.BatchProcessing(sourcePath, targetPath); Expect(result, Is.StringStarting("Success")); Assert.IsTrue(File.Exists(targetPath)); } |
NUnit 3 no longer sets the directory containing the test assembly as the working directory. Therefore you need to extend your relative paths with the TestContext.CurrentContext.TestDirectory property:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[Test] public void Data_can_be_loaded_from_file_system() { var sourcePath = TestContext.CurrentContext .TestDirectory + @"\TestData\2017.txt"; var targetPath = TestContext.CurrentContext .TestDirectory + @"\TestResult\2017_calculated.txt"; var result = _testee.BatchProcessing(sourcePath, targetPath); Assert.That(result, Does.StartWith("Success")); Assert.IsTrue(File.Exists(targetPath)); } |
Ignore
If you didn’t want to run a test you simply annotated the test with the IgnoreAttribute:
1 2 3 4 5 6 |
[Ignore] [Test] public void Something() { // Nobody knows why this no longer runs } |
That worked well but lead to ignored tests no one could remember why they should not be run. NUnit 3 forces you to give a reason:
1 2 3 4 5 6 |
[Ignore("reason why this test is ignored")] [Test] public void Something() { // Nobody knows why this no longer runs } |
Conclusion
Moving to NUnit 3 was a lot more work than I expected. I hope this post can help to reduce the time you need to get back on a current version. It’s worth it and I hope the move to 4.x will need less work.