C# Unit Testing
Published: 2022-06-06 14:14:28

# Overview
This guide is a basic introduction to Unit Testing in C#.

## Introduction
Check that your code is working as expected by creating and running unit tests. It's called unit testing because you break down the functionality of your program into discrete testable behaviors that you can test as individual units. Unit Testing is a software testing approach which is performed at development time to test the smallest component of any software. Unit test cases help us to test and figure out whether the individual unit is performing the task in a good manner or not. 

## Why do we need the unit test?
One of the most valuable benefits of using Unit Tests for your development is that it may give you positive confidence that your code will work as you have expected it to work in your development process. Unit Tests always give you the certainty that it will lead to a long term development phase because with the help of unit tests you can easily know that your foundation code block is totally dependable on it.

There are a few reasons that can give you a basic understanding of why a developer needs to design and write out test cases to make sure major requirements of a module are being validated during testing:
- Unit testing can increase confidence and certainty in changing and **maintaining code** in the **development process**.
- Unit testing always has the ability to **find problems in early stages** in the development cycle.
- Codes are **more reusable**, **reliable** and **clean**.
- **Development** becomes **faster**.
- Easy to **automate**.

## Unit Test Rules  
There are a lot of rules in unit testing that we have mentioned some important of them here:

### 1. Follow SOLID Principles:
SOLID principles provide us with ways to move from tightly coupled code and little encapsulation to the desired results of loosely coupled and encapsulated real needs of a business properly.

### 2. Make the code loosely coupled:
Loose coupling is preferred since through it changing one class will not affect another class. It reduces dependencies on a class. That would mean you can easily reuse it. Gets less affected by changes in other components.
```cs
public interface IUserRepository{
	User GetById(int);
}
public class UserRepository : IUserRepository{
	public User GetById(int) { 
	...
	}
}

public class UserService {
	// use dependency injection to create your dependencies outside of the class
	public UserService(IUserRepository userRepository)
}
```

### 3.  Use IoC instantiation instead of hard-code instantiation (Avoid Tight coupling)

- IoC instantiation (`Loosely coupled`)
```c#
[ApiController]
[Route("api/v1/tools")]
public class ToolsController : ControllerBase
{
	private readonly IToolService _toolService;
	public ToolsController(IToolService toolService)
	{
	   _toolService = toolService ?? throw new ArgumentNullException(nameof(toolService));
	}
}
```

- Hard-code instantiation (`Tight coupling`) 
```c#
[ApiController]
[Route("api/v1/tools")]
public class ToolsController : ControllerBase
{
	private readonly IToolService _toolService;
	public ToolsController()
	{
	   _toolService = new ToolService();
	}
}
```
 
### 4. Use IOptions<T> pattern instead of const variable
The options pattern uses classes to provide strongly typed access to groups of related settings. When configuration settings are isolated by scenario into separate classes, the app adheres to two important software engineering principles:
- **Encapsulation** - Classes that depend on configuration settings depend only on the configuration settings that they use.
- **Separation of Concerns** - Settings for different parts of the app aren't dependent or coupled to one another.

**Use case:**
- Use Options pattern
```json
// appsettings.json
{
"FilePath": {
        "LogFilePath": "./Logs"
    }
}
```
```c#
public class FileSetting
{
    public string LogFilePath { get; set; }
}

public class ToolService : IToolService
{
    private readonly FileSetting _fileSetting;

    public ToolService(IOptions<FileSetting> fileSettingOption)
    {
        _fileSetting = fileSettingOption.Value ?? throw new ArgumentNullException(nameof(fileSettingOption));
    }
}
```
- Use Const variable
```c#
public static class FileSetting {
	public const string LogFilePath = "./Logs" 
}
```

### 5. Write Unit Test Cases only for small functionality

### 6. Unit test naming conventions
Test naming is important for teams on a long-term project as any other code style conventions. By applying code convention in tests you proclaim that each test name will be readable, and understandable and will have a well-known naming pattern for everyone on the project.
- Unit test **class** name pattern 
```cs
// Pattern : ClassName + Tests
// UserService class => UserServiceTests
// UserRepository class => UserRepositoryTests
```
- Unit test **method** name pattern
```cs
// Pattern : MethodName_StateUnderTest_ExpectedBehavior
// Method Name : GetById(id?)
// GetById_IdIsNotValid_ReturnsBadData
// GetById_UserNotFound_ReturnsItemNotFoundSr
// GetById_UserIdIsNull_ThrowsInvalidArgumentException
```

### 7. If a function is performing so many operations then just write Unit Test Case for each individual function
```cs
public class UserService {
	public ServiceResponse<User> GetById(int id) {
		if( id == 0 ){
			return new BadDataSr<User>("id is not valid"); // The first unit test
		}
		var result = _userRepository(id);
		if(result == null) {
			return new ItemNotFoundSr<User>("user not found"); // The second unit test
		}
		// The third unit test
		return result;
	}
}

// We need to write three unit tests for the GetById method.
public class UserServiceTests {

[Fact]
void GetById_IdIsNotValid_ReturnsBadData(){}

[Fact]
void GetById_UserNotFound_ReturnsItemNotFoundSr(){}

[Fact]
void GetById_ValidData_ReturnsUser(){}

}
```
	
### 8. Don't write Unit Test Cases which are dependent on another Unit Test Case
### 9. The name of the function for the Unit Test Case should be self-explanatory
### 10. Unit Test Cases should always be independent.
### 11. Performance-wise, Unit Test Case should always be fast

## Arrange-Act-Assert Test Pattern:
Arrange-Act-Assert is a great way to structure test cases. It prescribes an order of operations:

1. **Arrange** inputs and targets. Arrange steps should set up the test case. Does the test require any objects or special settings? Does it need to prep a database? Does it need to log into a web app? Handle all of these operations at the start of the test.
2. **Act** on the target behavior. Act steps should cover the main thing to be tested. This could be calling a function or method, calling a REST API, or interacting with a web page. Keep actions focused on the target behavior.
3. **Assert** expected outcomes. Act steps should elicit some sort of response. Assert steps verify the goodness or badness of that response. Sometimes, assertions are as simple as checking numeric or string values. Other times, they may require checking multiple facets of a system. Assertions will ultimately determine if the test passes or fails.

### Example
```c#
[Fact]
void GetById_IdIsNotValid_ReturnsBadData()
{

// Arrange
var userService = new UserService();
int id = 0;

// Act
var response = userService.Get(id);

// Assert
Assert.Equal("id is not valid", response.Message);
}
```


## Code coverage tooling
Unit tests help to ensure functionality and provide a means of verification for refactoring efforts. Code coverage is a measurement of the amount of code that is run by unit tests - either lines, branches, or methods.
```bash
 # https://github.com/danielpalme/ReportGenerator
 dotnet tool install -g dotnet-reportgenerator-globaltool
 
 dotnet test --collect:"XPlat Code Coverage" 
 
 reportgenerator -reports:"\TestResults\**\coverage.cobertura.xml" -targetdir:"coveragereport" -reporttypes:Html
```