Mockování je důležitým konceptem při psaní jednotkových testů z několika důvodů:
- Izolace od závislostí: Mockování umožňuje oddělit testovanou třídu od jejích závislostí. To znamená, že při testování konkrétní třídy nemusíte brát v potaz implementaci jejích závislostí. Místo toho můžete vytvořit mock objekty, které simulují chování těchto závislostí.
- Opakovatelnost a konzistence: Při použití skutečných závislostí, jako jsou databáze nebo externí API, mohou testy být nekonzistentní a záviset na stavu těchto závislostí. Použití mocků umožňuje vytvořit konzistentní a opakovatelné testy, které nejsou závislé na externích faktorech.
- Rychlost testů: Skutečné závislosti mohou být pomalé nebo nedostupné v určitých situacích, což může zpomalit běh testů. Použití mocků umožňuje rychlejší spouštění testů, protože mocky jsou obvykle rychlejší než skutečné závislosti.
- Izolace chyb: Pokud test selže, mocky vám umožňují izolovat problém na konkrétní část kódu, kterou testujete. Bez mocků by selhání mohlo být způsobeno problémem v externí závislosti, což by mohlo být složité odhalit.
- Testování hraničních podmínek a chybových stavů: Mocky umožňují snadno simulovat různé situace, včetně chybových stavů, které mohou být obtížné dosáhnout s reálnými závislostmi.
- Zvýšení rychlosti vývoje: Testy s mocky umožňují rychlejší iterace při vývoji. Můžete psát testy pro nový kód, i když jeho závislosti nejsou ještě implementovány.
- Snížení nákladů na infrastrukturu: Pokud byste všechny testy spouštěli s reálnými závislostmi, mohlo by to vyžadovat drahou infrastrukturu, například databázový server. Mockování umožňuje testování bez skutečné infrastruktury.
Celkově mockování je důležitou technikou pro vytváření izolovaných a efektivních jednotkových testů, které vám pomáhají zajistit kvalitu kódu a snižovat rizika chyb.
Instalujeme Moq
Abychom mohli začít mockovat doinstalujeme Nuget do projektu s testy:
Install-Package Moq
Kód pro otestování
Mějme controller a service, které chceme otestovat. Service se do controlleri injectne přes DI:
ProductController.cs:
namespace WebAPiDi.Controllers
{
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
readonly IProductService _productService;
public TestController(IProductService productService)
{
_productService = productService;
}
[HttpGet]
public IActionResult Shop()
{
return Ok(_productService.Shop());
}
}
}
ProductService.cs:
namespace WebAPiDi.Services
{
public class ProductService : IProductService
{
public IEnumerable<ProductDto> Shop()
{
return new List<ProductDto>()
{
new ProductDto() { Id = 1, Name = "Best product" }
};
}
}
}
IProductService.cs:
namespace WebAPiDi.Services
{
public interface IProductService
{
IEnumerable<ProductDto> Shop();
}
}
productDto.cs:
namespace WebAPiDi.Dtos
{
public class ProductDto
{
public int Id { get; set; }
public required string Name { get; set; }
}
}
Program.cs (konfigurace sávislostí):
builder.Services.AddScoped<IProductService, ProductService>();
Testujeme
V minulých dílech jsme si ukázali jak zprovoznit NUnit a Moq. Dnes už je jdeme rovnou používat.
Testujeme service
Začneme tím jednodušším – otestujme ProducService. Nepotřebujeme nic mockovat, pouze si uděláme instanci ProuctService a zavoláním metody Shop zkontrolujeme že vrátí 1 záznam, ve kterém zkontrolujeme vyplnění propert:
namespace Test.Services
{
[TestFixture]
public class ProductServiceTests
{
[Test]
public void Shop_ReturnsListOfProducts()
{
// Arrange
IProductService productService = new ProductService();
// Act
IEnumerable<ProductDto> products = productService.Shop();
// Assert
Assert.IsNotNull(products);
Assert.IsInstanceOf<IEnumerable<ProductDto>>(products);
Assert.That(products.Count(), Is.EqualTo(1));
foreach (var product in products)
{
Assert.IsNotNull(product.Id);
Assert.IsNotNull(product.Name);
}
}
}
}
Při testování controlleru budeme muset použít mockování. Namockujeme se ProductService, čímž vytvoříme izovali od této service a můžeme si nasimulovat nejrůznější chování této service.
namespace Test.Controllers
{
[TestFixture]
public class TestControllerTests
{
[Test]
public void Shop_ReturnsOkResultWithProducts()
{
// Arrange
var productServiceMock = new Mock<IProductService>();
productServiceMock.Setup(repo => repo.Shop()).Returns(new List<ProductDto>
{
new ProductDto() { Id = 68, Name = "Jarda Jagr" },
new ProductDto() { Id = 88, Name = "pasta" }
});
var controller = new TestController(productServiceMock.Object);
// Act
var result = controller.Shop() as OkObjectResult;
// Assert
Assert.IsNotNull(result);
Assert.That(result.StatusCode, Is.EqualTo(200));
var products = result.Value as List<ProductDto>;
Assert.IsNotNull(products);
Assert.That(products.Count, Is.EqualTo(2));
}
[Test]
public void Shop_ReturnsEmptyProducts()
{
// Arrange
var productServiceMock = new Mock<IProductService>();
productServiceMock.Setup(repo => repo.Shop()).Returns(new List<ProductDto>());
var controller = new TestController(productServiceMock.Object);
// Act
var result = controller.Shop() as OkObjectResult;
// Assert
Assert.IsNotNull(result);
Assert.That(result.StatusCode, Is.EqualTo(200));
var products = result.Value as List<ProductDto>;
Assert.IsNotNull(products);
Assert.That(products.Count, Is.EqualTo(0));
}
}
}