Showing posts with label Integration Testing. Show all posts
Showing posts with label Integration Testing. Show all posts

Monday, 5 December 2022

CI/CD with GitHub Actions pipeline to run the .Net unit test and publish results

 Writing Tests (Unit Tests, Integration Tests) is not only the best practice but also essential to ensure quality code.

From the DevOps side, it is essential to put a gate to check for successful unit test execution with each pull request or push. So in this article, we will see how to implement a pipeline to run the unit tests and publish the results.

What do we need?

  1. We need a pipeline to be triggered with every pull request for your code repo.
  2. Jobs to run the unit tests and publish the results.

Please follow my other articles to learn more about GitHub Actions: CI/CD with GitHub ActionsCI/CD with GitHub Actions to deploy Applications to Azure App ServiceCI/CD with GitHub Actions to deploy Application to Azure Kubernetes Cluster

Let’s create our pipeline for unit test execution. To do this add a yaml/yml file as .github/workflows/buildAndRunTest.yml in the root project folder and then starts with defining the name & triggering action.

name: Build and run tests

env:
DOTNET_VERSION: '6.0' # set this to the .NET Core version to use
WORKING_DIRECTORY: './src' #define the root folder path

on:
workflow_dispatch: #for manual trigger
pull_request: #trigger action with pull request
branches:
- main #your branch name

Next step, we will define the job and mention the target machine (Linux/windows) then perform the necessary steps for code checkout and setting .net core

jobs:
build:
runs-on: ubuntu-latest #target machine is ubuntu

steps:
- uses: actions/checkout@v2

- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }} #reading it from env variable defined above

I’m using “EnricoMi/publish-unit-test-result-action” action from the marketplace with JUnit logger hence we need to install the related package for each test project as the next step.

Xml logger for JUnit v5 compliant xml report when test is running with “dotnet test” or “dotnet vstest”.

- name: Setup test loggers
shell: pwsh
run: |
foreach ($file in Get-ChildItem -Path .src/tests/*Tests.csproj -Recurse)
{
dotnet add $file package JunitXml.TestLogger --source 'https://api.nuget.org/v3/index.json'
}

From the above code, looping through all test projects inside the src/tests folder.

Next step we will execute the tests with dotnet command.

- name: Run unit tests
shell: bash
working-directory: ./src
run: |
dotnet test myDemoApp.sln --logger 'junit;LogFileName=TestResults.xml' \
--verbosity normal --results-directory ./_test-results

The above code will run all the unit tests in the project and save the result inside the src/_test-results/TestResults.xml file.

If you have categorized tests then you can use the filter option with dotnet test as:
dotnet test — filter “TestCategory=CategoryA”

Finally will publish the results using “EnricoMi/publish-unit-test-result-action” from the marketplace.

 - name: Publish Unit Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
junit_files: ./src/_test-results/*.xml

We are done so With this if you the actions pipeline you will see the results as:

Note: There are various actions available in the GitHub marketplace to publish your test results, please explore them too.

Hope you enjoyed the content, follow me for more like this, and please don’t forget to LIKE  it. Happy programming.


Monday, 27 June 2022

ASP.NET Core Integration test using Moq Framework

 Testing coverage is the key part of any project development and Testing coverage is not only about the unit testing instead Integration Testing is extremely important to make sure entire system is unbroken & bug free with any change or subsequent enhancement.

namespace CoreWebAPIMoqDemo.Services
{
public interface IWeatherForecastService
{
Task<WeatherForecast[]> GetWeatherForecast();
}
public record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}
namespace CoreWebAPIMoqDemo.Services
{
public class WeatherForecastService : IWeatherForecastService
{
string[] summaries = new string[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

public async Task<WeatherForecast[]> GetWeatherForecast()
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateTime.Now.AddDays(index),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
)).ToArray();
return forecast;
}

}
}
using CoreWebAPIMoqDemo.Services;var builder = WebApplication.CreateBuilder(args);// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<IWeatherForecastService, WeatherForecastService>();
var app = builder.Build();// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();app.MapGet("/weatherforecast", async (IWeatherForecastService service) =>
{
return await service.GetWeatherForecast();
})
.WithName("GetWeatherForecast");
app.Run();public partial class Program { }
<PackageReference Include="Moq" Version="4.18.1" />
using Moq;
using CoreWebAPIMoqDemo.Services;
using System.Reflection;
namespace CoreWebAPIMoqDemo.Tests
{
internal class MockServices
{
public Mock<IWeatherForecastService> WeatherForecastServiceMock { get; init; }
public MockServices()
{
WeatherForecastServiceMock = new Mock<IWeatherForecastService>();
}
/// <summary>
/// This returns the collection of all mock service's interface type and the mock object, defined here i.e. <see cref="WeatherForecastServiceMock"/>.
/// </summary>
/// <returns></returns>
public IEnumerable<(Type, object)> GetMocks()
{
return GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Select(x =>
{
var interfaceType = x.PropertyType.GetGenericArguments()[0];
var value = x.GetValue(this) as Mock;
return (interfaceType, value.Object);
})
.ToArray();
}
}
}
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using CoreWebAPIMoqDemo.Services;
namespace CoreWebAPIMoqDemo.Tests
{
internal class TestMoqPOCApplication : WebApplicationFactory<Program>
{
private readonly MockServices _mockServices;
public TestMoqPOCApplication(MockServices mockServices)
{
_mockServices = mockServices;
}
protected override IHost CreateHost(IHostBuilder builder)
{
builder.ConfigureServices(services => {
foreach ((var interfaceType, var serviceMock) in _mockServices.GetMocks())
{
services.Remove(services.SingleOrDefault(d => d.ServiceType == interfaceType));
services.AddSingleton(typeof(IWeatherForecastService), serviceMock);
}

});
return base.CreateHost(builder);

}
}
}
using Moq;
using CoreWebAPIMoqDemo.Services;
using Newtonsoft.Json;
namespace CoreWebAPIMoqDemo.Tests
{
public class WeatherForecastTests
{
private readonly TestMoqPOCApplication _testMoqPOCApplication;
private readonly MockServices _mockServices;
public readonly HttpClient _client;

public WeatherForecastTests()
{
_mockServices = new MockServices();
//instantiating TestMoqPOCApplication
_testMoqPOCApplication = new TestMoqPOCApplication(_mockServices);
//creating client for api call
_client = _testMoqPOCApplication.CreateClient();
}
[Fact]
public async void GetWeatherForecastTest()
{
//mocking the business service's GetWeatherForecast() method to return result as below
var expResult = new WeatherForecast[]
{
new WeatherForecast(DateTime.Now, 26, "Bengaluru")
};

//mocking the business service's GetWeatherForecast()
_mockServices.WeatherForecastServiceMock.Setup(m => m.GetWeatherForecast()).ReturnsAsync(expResult);
//calling the api
var response = await _client.GetAsync("/weatherforecast");
string jsonString = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<WeatherForecast[]>(jsonString);
//testing the response
Assert.Equal(response.StatusCode, System.Net.HttpStatusCode.OK);
Assert.Equal(expResult, result);
}
}
}