Having previously searched for (and found) a number of videos on YouTube on the topic of unit testing with XUnit and Moq, I didn't find anything satisfactory until today. However, what I did find was Tim Corey's introductory videos. They are rather long-winded and he take a lot of time to convey the important points at rather low volume, which can be difficult to hear clearly. Here, then, is a collection of condensed notes on the basics of unit testing in Visual Studio, using XUnit (a successor to NUnit, written by the creator of NUnit).
In the below, an item in curly brackets/braces ({}) designates a substitution. EG: In "{ApplicationOrLibraryNameUnderTest}
.Tests
", "ApplicationOrLibraryNameUnderTest
" is the name of the application or library for which you'll be writing and running tests.
- When using XUnit and/or Moq for unit testing, do not create a project from the "Tests" category on the project creation screen. These projects are for testing with MS Tests. Instead, create a new .Net Framework (or Core) Class Library and make sure that the project is named
{ApplicationOrLibraryNameUnderTest}
.Tests
. (This is a well-used convention.) - When testing with XUnit, you'll want the test project to include references to the project being tested, as well as XUnit. (From the NuGet package repository, install
xunit, xunit.runner.console
andxunit.runner.visualstudio
.) - When naming a test class, use the convention
{ClassName}Tests.
- When naming a test method, use the convention of
{MethodUnderTest}_Should{DoSomething}
. "DoSomething" describes what the method should do or what the expected result is. Eg:CheckFileExists_ShouldThrowFileNotFoundException
- Don't worry if the names of the test methods end up being long; the purpose is to make them descriptive so you know what the test is expected to do.
- A unit test usually consists of three parts: setup (Arrangement), action (call the method and get a result) and an assertion (confirm the result is as expected). Sometimes, setup is not required. Additionally, the action and assertion are combined, as is the case when testing that an exception is thrown.
- In the arrangement part, perform any steps necessary for starting the test (such as setting values for variables). Here, set the value of the expected result (usually a literal, if possible). By convention, this is usually named "
expected
". - In the action section, call the method under test and store the result in a variable named "
actual
" or "result
". (This might not always be possible.) - In the assertion phase, use at least one of the methods from the
Assert
class (fromxunit
; you'll need ausing
statement). - In order to accurately test something, you might need one or more assertions (for different conditions) for the test to pass. (There's no rule that dictates that a test must contain only one assertion, despite what some dogmatic unit testers might say.)
- Don't forget to decorate the test method, usually with
[Fact]
for a single test. - As opposed to a
[Fact]
, a[Theory]
allows for running the same test with different datasets. (For every dataset, a new test entry is shown in the test explorer.) - The simplest way to provide a dataset for a theory is to use
[InlineData({literal0, literal1, literalN})]
(where literalN is the expected result). A dataset can be provided in other ways, butInlineData
is the simplest. - To make test methods discoverable/visible in the "Test Explorer/Runner" dialog/panel, build the solution after adding tests. (It is also possible to configure the tests to run after build, but this might not work with VS CE and/or XUnit.)
- Adding tests after you have working code is not as great as writing them before you have code to test. (Knowing what you want to test and how before you write it can help you think about your code and ensure that it's correct before and while you write and modify it, since tests first fail if the code under test doesn't exist and pass once it's correct.) This (red-green refactoring, referring to result colours) is one of the key tenets of test-driven development (TDD).
- Despite what fanatics might claim, you really don't need to test everything in your code, provided you test most of it (especially the most-important/referenced and most complex code). You don't need to have 100% code coverage; 20-25% coverage is still better than no coverage.
- You can create a
xunit.runner.json
configuration file to configure how the test explorer/runner dialog behaves. Be sure to tick the "Copy on build" checkbox in the "Properties" dialog for it. (Setting a"methodDisplay"
property to"method"
within the JSON will cause only the test method's name to show, instead of the fully qualified path to it.)
I have more notes on this topic, but it's getting late as I write this and I need to get to bed and up early in the morning. To be continued ...