You are on page 1of 7

What are unit tests?

Having automated tests is a great way to ensure a software application does what its
authors intend it to do. There are multiple types of tests for software applications.
These include integration tests, web tests, load tests, and others. Unit tests test
individual software components and methods. Unit tests should only test code within
the developer’s control. They should not test infrastructure concerns. Infrastructure
concerns include databases, file systems, and network resources.
Also, keep in mind there are best practices for writing tests. For example, Test Driven
Development (TDD) is when a unit test is written before the code it is meant to check. TDD is
like creating an outline for a book before we write it. It is meant to help developers write
simpler, more readable, and efficient code.

Characteristics of a good unit test


 Fast. It is not uncommon for mature projects to have thousands of unit tests. Unit tests
should take very little time to run. Milliseconds.
 Isolated. Unit tests are standalone, can be run in isolation, and have no dependencies
on any outside factors such as a file system or database.
 Repeatable. Running a unit test should be consistent with its results, that is, it always
returns the same result if you do not change anything in between runs.
 Self-Checking. The test should be able to automatically detect if it passed or failed
without any human interaction.
 Timely. A unit test should not take a disproportionately long time to write compared
to the code being tested. If you find testing the code taking a large amount of time
compared to writing the code, consider a design that is more testable.

Arranging your tests

Arrange, Act, Assert is a common pattern when unit testing. As the name implies, it
consists of three main actions:

 Arrange your objects, creating and setting them up as necessary.


 Act on an object.
 Assert that something is as expected.

Why?

 Clearly separates what is being tested from the arrange and assert steps.
 Less chance to intermix assertions with "Act" code.

Bad:
[Fact]
public void Add_EmptyString_ReturnsZero()
{
// Arrange
var stringCalculator = new StringCalculator();

// Assert
Assert.Equal(0, stringCalculator.Add(""));
}
Better:
[Fact]
public void Add_EmptyString_ReturnsZero()
{
// Arrange
var stringCalculator = new StringCalculator();

// Act
var actual = stringCalculator.Add("");

// Assert
Assert.Equal(0, actual);
}
Here, we will create four different test methods to test four different methods. And each
method will be gone through with three processes -- one arranges the data for testing, the
second performs the operation with required values and the last checks if the expected value
is equal to the actual value or not.
Fluent Assertions is a very extensive set of extension methods that allows you to more
naturally specify the expected outcome of a TDD or BDD-style unit tests. Targets .NET
Framework 4.5 and 4.7, as well as .NET Core 2.0, .NET Standard 1.3, 1.6 and 2.0.

We need to keep three things while writing the Unit Test Cases and these
are Arranging the data, Performing the action and Matching the output
(Arrange, Act, Assert).

A Working Theory
XUnit also has a Theory attribute, which represents a test that should succeed for certain input
data. In practice, most code has a different behavior depending on inputs (such as a different
result based on validation), and I find that I use Theory to create parameterized tests much more
often than Fact. There are 3 basic ways to create Theory based tests, and these ways will be
covered below.

Theory with InlineData Attribute


The easiest way to create a Theory based test is to use the InlineData attribute. Our test
below takes 2 parameters and adds them together and tests the result. Instead of writing 3 tests,
we create 3 InlineDataattributes with different parameter values. Now we have 3 test cases
with very little additional code!
Hide Copy Code

[Theory]
[InlineData(2,3)]
[InlineData(4,5)]
[InlineData(5,11)]
public void CanAddNumbersFromInlineDataInput(int x, int y)
{
// arrange
Abacus abacus = new Abacus(Math.Max(x, y), x + y);

// act
int result = abacus.Add(x, y);

// assert
Assert.True(result > 0);
Assert.Equal(x + y, result);
}

I tend to use this form when the number of parameterized cases is pretty small.

Theory with MemberData Attribute


Another way to create a Theory based test is to use the MemberData attribute to provide the
parameter information. In our add test below, the MemberData attribute provides
the AddPositiveNumberData list to run the parameterized tests. Again, 3 different test cases
are run with different parameters.

Hide Copy Code

[Theory]
[MemberData("AddPositiveNumberData")]
public void CanAddNumbersFromMemberDataInput(int x, int y)
{
// arrange
Abacus abacus = new Abacus(Math.Max(x, y), x + y);

// act
int result = abacus.Add(x, y);

// assert
Assert.True(result > 0);
Assert.Equal(x + y, result);
}

private static List<object[]> AddPositiveNumberData()


{
return new List<object[]>
{
new object[] {1, 2},
new object[] {2, 2},
new object[] {5, 9}
};
NUnit 3.x (Constraint) MSTest 15.x xUnit.net 2.x Comments

MSTest and xUnit.net support


Is.EqualTo AreEqual Equal generic versions of this method

MSTest and xUnit.net support


Is.Not.EqualTo AreNotEqual NotEqual generic versions of this method

Is.Not.SameAs AreNotSame NotSame

Is.SameAs AreSame Same

Does.Contain Contains Contains

Does.Not.Conta DoesNotCont DoesNotCo


in ain ntain

DoesNotTh Ensures that the code does not


Throws.Nothing n/a
row throw any exceptions

xUnit.net
n/a Fail n/a alternative: Assert.True(f
alse, "message")

xUnit.net
Is.GreaterThan n/a n/a alternative: Assert.True(x
> y)

Ensures that a value is in a given


Is.InRange n/a InRange inclusive range

Is.AssignableF IsAssigna
n/a
rom bleFrom

Is.Empty n/a Empty

Is.False IsFalse False


NUnit 3.x (Constraint) MSTest 15.x xUnit.net 2.x Comments

Is.InstanceOf< IsInstanceO
IsType<T>
T> fType

xUnit.net
Is.NaN n/a n/a alternative: Assert.True(d
ouble.IsNaN(x))

xUnit.net
Is.Not.Assigna alternative: Assert.False(
n/a n/a
bleFrom<T> obj is Type)

Is.Not.Empty n/a NotEmpty

Is.Not.Instanc IsNotInstan IsNotType


eOf<T> ceOfType <T>

Is.Not.Null IsNotNull NotNull

Is.Null IsNull Null

Is.True IsTrue True

xUnit.net
Is.LessThan n/a n/a alternative: Assert.True(x
< y)

NotInRang Ensures that a value is not in a


Is.Not.InRange n/a
e given inclusive range

Throws.TypeOf< Ensures that the code throws an


n/a Throws<T>
T> exact exception

Attribute Notes
Note 1: Long-term use of [ExpectedException] has uncovered various problems with
it. First, it doesn’t specifically say which line of code should throw the exception, which allows
subtle and difficult-to-track failures that show up as passing tests. Second, it doesn’t offer the
opportunity to fully inspect details of the exception itself, since the handling is outside the
normal code flow of the test. Assert.Throws allows you to test a specific set of code for
throwing an exception, and returns the exception during success so you can write further
asserts against the exception instance itself.

Note 2: The xUnit.net team feels that per-test setup and teardown creates difficult-to-follow
and debug testing code, often causing unnecessary code to run before every single test is
run. For more information, see http://jamesnewkirk.typepad.com/posts/2007/09/why-you-
should-.html.

Note 3: xUnit.net provides a new way to think about per-fixture data with the use of
the IClassFixture<T> and ICollectionFixture<T> interfaces. The runner will
create a single instance of the fixture data and pass it through to your constructor before
running each test. All the tests share the same instance of fixture data. After all the tests have
run, the runner will dispose of the fixture data, if it implements IDisposable. For more
information, see Shared Context.

Note 4: xUnit.net ships with support for data-driven tests call Theories. Mark your test with
the [Theory] attribute (instead of [Fact]), then decorate it with one or
more [XxxData] attributes, including [InlineData] and [MemberData]. For more
information, see Getting Started.

Attributes
NUnit 3.x MSTest 15.x xUnit.net 2.x Comments

[Test] [TestMethod] [Fact] Marks a test method.

xUnit.net does not require an


[TestFixtur attribute for a test class; it
[TestClass] n/a looks for all test methods in all
e] public (exported) classes in the
assembly.

xUnit.net has done away with


Assert.That Assert.Throws the ExpectedException
[ExpectedExc
Record.Exce Record.Except attribute in favor
eption] of Assert.Throws.
ption ion
See Note 1

We believe that use


of [SetUp] is generally bad.
[TestInitial
[SetUp] Constructor However, you can implement a
ize]
parameterless constructor as a
direct replacement. See Note 2
NUnit 3.x MSTest 15.x xUnit.net 2.x Comments

We believe that use


of [TearDown] is generally
[TestCleanup IDisposable.D bad. However, you can
[TearDown]
] ispose implement IDisposable.
Dispose as a direct
replacement. See Note 2

To get per-class fixture setup,


[OneTimeSet [ClassInitia IClassFixture implement IClassFixtur
Up] lize] <T> e<T> on your test class.
See Note 3

To get per-class fixture


teardown,
[OneTimeTea [ClassCleanu IClassFixture
implement IClassFixtur
rDown] p] <T>
e<T> on your test class.
See Note 3

To get per-collection fixture


setup and teardown,
ICollectionFi
n/a n/a implement ICollectionF
xture<T>
ixture<T> on your test
collection. See Note 3

Set the Skip parameter on


[Ignore("re [Fact(Skip="r
[Ignore] the [Fact] attribute to
ason")] eason")]
temporarily skip a test.

[TestPropert Set arbitrary metadata on a


[Property] [Trait]
y] test

[Theory] Theory (data-driven test).


[Theory] [DataSource]
[XxxData] See Note 4

The difference is that unit tests are designed to test every functional code block in your project in
an isolated state, whereas integration tests test against the controller actions. This different
approach is highly suitable for web projects, as the tests are effectively emulating user action on
views.