Testing is a huge component of any developer’s workflow, and tracking down errors can be one of the .NET developer’s most time-consuming tasks. It stands to reason that the better the test, the more QA cycles you save, and the faster your software gets to production. This becomes even more critical as your stack (and business) grows. Another positive collateral effect of implementing tests is that the code becomes clean and easier to maintain, and ultimately of higher quality.

To improve test quality, you must first choose the right testing methodology.

The difference between unit, integration, and functional tests

A unit test verifies that a relatively small and specific piece of code functions as intended. The scope of this testing is narrow by design. For example, a unit test might check that a calculation outputs the correct value, or that methods validate their inputs. Unit tests shouldn’t hit a database or call external services.

An integration test confirms that different components of the system can work correctly together. Contrary to unit tests, integration tests might talk to a database, or test multiple services. For example, your application perhaps depends on the Stormpath REST API for authentication, so an integration test is used to determine that both are working correctly together.

A functional test tests an application against high-level functional requirements. This test is focused on the user’s perspective and primarily checks the user interface. An example of a functional test would be emulating a specific user’s input and verifying the expected results in the screen.

Now that we’ve defined the difference between these common test types, let’s look at a few ways to improve your tests.

1. Start with Test-Driven Development

Writing your test at the same time you write your code, or even before it, is referred to as Test-Driven Development and has proven to be a powerful approach. By applying this method, the code and tests will work as one by design. Additionally, you will catch bugs before you dive into building other functionalities. And, what is by far the greatest value, by writing your test firsts you can refactor your code at later stage and the test will alert you to any breakage.

We believe that the best practice is to make automated testing is part of the initial project scope, rather than an add-on when the project is nearing completion. Take the time to build well-reasoned, quality tests first. Done right, it’ll save you time in the long run.

2. Limit test scope

By limiting the scope of individual test to small, specific units or functions, you ensure that the test will only fail if the function under test fails. This is where time savings truly comes into play, as properly limited tests should make errors obvious. When in doubt, write more, smaller tests.

In this example, the test could fail because the activation is not successful, an error which is critically important to uncover. However, the error will be obscured by the actual the scope of the test, which is bank account creation.

[TestMethod] public void Should_Create_Bank_Account_For_New_User() { //Arrange var newUser = new User("Name", "Surname", "Email"); var newBankAccount = new BankAccount(); //Act bool activated = newUser.Activate(); // Could fail the activation bool created = false; if (activated) bool created = newUser.CreateBankAccount(); //Assert Assert.IsTrue(created); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [ TestMethod ] public void Should_Create_Bank_Account_For_New_User ( ) { //Arrange var newUser = new User ( "Name" , "Surname" , "Email" ) ; var newBankAccount = new BankAccount ( ) ; //Act bool activated = newUser . Activate ( ) ; // Could fail the activation bool created = false ; if ( activated ) bool created = newUser . CreateBankAccount ( ) ; //Assert Assert . IsTrue ( created ) ; }

To solve the problem in this example we need create a dummy user who is already activated, so we can mock that user for purposes of the test.

Pro tip: Unit tests should never hit the actual database and instead should be designed to work with fake data. This ensures that your unit tests are fast and only test one thing at a time.

3. Use a mocking framework to abstract behavior

A mocking framework will allow you to create dummy implementations of any object or class. With mocked objects, you can write tests against the expected behavior of other classes that are outside the scope of the test.

In the example below, we are testing that an authenticated user will be sent to the specified home page. In this case, we need to mock two things:

1. The ControllerContext to be able to execute the controller

2. The HTTP request to set the property IsAuthenticated

The point here is that normally, it’s difficult to test things like HTTP request handlers because you can’t do it in an automated way unless you have some tool sending actual HTTP requests. With mocking you can “fake” the HTTP request (the “context”) so you can test your logic without having to manually send real HTTP requests.

[TestMethod] public void Home_Page_Should_Load_Authenticated_Version() { // Arrange var mock = new Mock<ControllerContext>(); mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true); var controller = new HomeController(); controller.ControllerContext = mock.Object; // Act ViewResult result = controller.Index() as ViewResult; // Assert Assert.IsNotNull(result); Assert.AreEqual("Authenticated-Home-Page", result.ViewName); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [ TestMethod ] public void Home_Page_Should_Load_Authenticated_Version ( ) { // Arrange var mock = new Mock < ControllerContext > ( ) ; mock . SetupGet ( p = > p . HttpContext . Request . IsAuthenticated ) . Returns ( true ) ; var controller = new HomeController ( ) ; controller . ControllerContext = mock . Object ; // Act ViewResult result = controller . Index ( ) as ViewResult ; // Assert Assert . IsNotNull ( result ) ; Assert . AreEqual ( "Authenticated-Home-Page" , result . ViewName ) ; }

The above code example utilizes the most popular open source mocking tool, moq.

4. Keep unit and integration tests separate

Organize tests within their proper scope, so you can easily control and scale your tests. Since integration tests consume more resources and are lower in performance, they should always be separated from unit tests. Unit tests should also be as atomic as possible; otherwise you risk them becoming integration tests instead

5. Test the UI too

Most developers, after completing the unit and eventual integration testing, forget to implement automated UI testing. This type of test normally takes place at the final stage of the development cycle when the user interface is mature enough to be testable.

Common UI elements to test are input validation, data operations navigation views, and usability. It’s also important to test any critical workflows, like registration and login, for errors that will impact user experience.

The two major tools for UI testing are Coded UI, which is part of Visual Studio, and Selenium, a powerful and popular open source project.

6. Remember: Order should never matter

If your unit tests are failing because of the order in which you’re running them, it’s a “code smell” that your test methodology or architecture is wrong. Every test must be independent and able to be run by itself, without any other dependency.

Got it? Great! Now go forth and write better tests in .NET! Or, wait… did our mention of authentication catch your eye? Learn more about how Stormpath supports complete Identity Management in .NET in our product documentation, or through any of these great resources:

– Token Authentication in ASP.NET Core

– Tutorial: Build an ASP.NET Core Application with User Authentication

– Tutorial: Deploy an ASP.NET Core Application on Linux with Docker