Pruebas unitarias de Razor Pages en ASP.NET Core
ASP.NET Core admite pruebas unitarias de aplicaciones de Razor Pages. Las pruebas de la capa de acceso a datos (DAL) y de los modelos de página ayudan a garantizar lo siguiente:
- Las partes de una aplicación de Razor Pages funcionan de forma independiente y conjunta como una unidad durante la creación de una aplicación.
- Las clases y los métodos tienen ámbitos de responsabilidad restringidos.
- Hay documentación adicional disponible sobre cómo debe comportarse la aplicación.
- Se detectan regresiones —que son errores que salen a la luz a raíz de las actualizaciones de código— en los procesos de compilación e implementación automatizados.
En este tema se da por hecho que el usuario posee conocimientos básicos de las pruebas unitarias y las aplicaciones de Razor Pages. Si no está familiarizado con los conceptos de prueba o aplicaciones de Razor Pages, vea los siguientes temas:
- Introducción a Razor Pages en ASP.NET Core
- Tutorial: Introducción a Razor Pages en ASP.NET Core
- Prueba unitaria de C# en .NET Core mediante pruebas de dotnet y xUnit
Vea o descargue el código de ejemplo (cómo descargarlo)
El proyecto de ejemplo se compone de dos aplicaciones:
Aplicación | Carpeta del proyecto | Descripción |
---|---|---|
Aplicación de mensajes | src/RazorPagesTestSample | Permite a un usuario agregar un mensaje, eliminar un mensaje, eliminar todos los mensajes y analizar mensajes (hallar la media de palabras por mensaje). |
Probar la aplicación | tests/RazorPagesTestSample.Tests | Sirve para realizar una prueba unitaria del modelo de página Index y la DAL de la aplicación de mensajes. |
Las pruebas se pueden ejecutar con las características de prueba integradas de un IDE, como Visual Studio. Si usa Visual Studio Code o la línea de comandos, ejecute el siguiente comando en un símbolo del sistema del directorio tests/RazorPagesTestSample.Tests:
dotnet test
Organización de la aplicación de mensajes
La aplicación de mensajes es un sistema de mensajes de Razor Pages con las siguientes características:
- La página Index de la aplicación (
Pages/Index.cshtml
yPages/Index.cshtml.cs
) proporciona una interfaz de usuario y métodos de modelo de página para controlar la adición, eliminación y análisis de mensajes (hallar la media de palabras por mensaje). - La clase
Message
(Data/Message.cs
) describe un mensaje con dos propiedades:Id
(clave) yText
(mensaje). Se necesita la propiedadText
, que está limitada a 200 caracteres. - Los mensajes se almacenan en la base de datos en memoria de Entity Framework†.
- La aplicación contiene una DAL en su clase de contexto de base de datos,
AppDbContext
(Data/AppDbContext.cs
). Los métodos de DAL se marcan comovirtual
, lo que permite realizar simulaciones en ellos para usarlos en las pruebas. - Si la base de datos está vacía al inicio de una aplicación, el almacén de mensajes se inicializa con tres mensajes. Estos mensajes inicializados también se usan en las pruebas.
†En el tema de EF, Pruebas con InMemory, se explica cómo usar una base de datos en memoria con las pruebas con MSTest. En este tema se usa el marco de pruebas xUnit. Los conceptos y las implementaciones de prueba de diferentes marcos de pruebas son similares, pero no idénticos.
Aunque la aplicación de ejemplo no usa el patrón del repositorio y no es un ejemplo eficaz del patrón Unit of Work (UoW), Razor Pages admite estos patrones de desarrollo. Para más información, vea Diseño del nivel de persistencia de infraestructura y Lógica del controlador de pruebas en ASP.NET Core (el ejemplo implementa el patrón del repositorio).
Organización de la aplicación de prueba
La aplicación de prueba es una aplicación de consola dentro de la carpeta tests/RazorPagesTestSample.Tests.
Carpeta de aplicación de prueba | Descripción |
---|---|
UnitTests |
|
Utilities | Contiene el método TestDbContextOptions empleado para crear nuevas opciones de contexto de base de datos para cada prueba unitaria de DAL, de modo que la base de datos se restablezca a su condición de línea base en cada prueba. |
El marco de pruebas es xUnit. El marco de trabajo de simulación de objetos es Moq.
Pruebas unitarias de la capa de acceso a datos (DAL)
La aplicación de mensajes tiene una DAL con cuatro métodos contenidos en la clase AppDbContext
(src/RazorPagesTestSample/Data/AppDbContext.cs
). Cada método tiene una o dos pruebas unitarias en la aplicación de prueba.
Método de DAL | Función |
---|---|
GetMessagesAsync |
Obtiene un elemento List<Message> de la base de datos ordenada por la propiedad Text . |
AddMessageAsync |
Agrega un elemento Message a la base de datos. |
DeleteAllMessagesAsync |
Elimina todas las entradas Message de la base de datos. |
DeleteMessageAsync |
Elimina todas las entradas Message de la base de datos según Id . |
Las pruebas unitarias de la DAL requieren DbContextOptions al crear un elemento AppDbContext
para cada prueba. Un método para crear el elemento DbContextOptions
de cada prueba es usar un elemento DbContextOptionsBuilder:
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
using (var db = new AppDbContext(optionsBuilder.Options))
{
// Use the db here in the unit test.
}
El inconveniente de este método es que cada prueba recibe la base de datos en el estado en el que la dejó la prueba anterior. Esto puede ser problemático al intentar escribir pruebas unitarias atómicas que no interfieran entre sí. Para forzar que AppDbContext
use un nuevo contexto de base de datos en cada prueba, proporcione una instancia de DbContextOptions
que esté basada en un nuevo proveedor de servicios. La aplicación de prueba muestra cómo llevar esto a cabo con el método TestDbContextOptions
de su clase Utilities
(tests/RazorPagesTestSample.Tests/Utilities/Utilities.cs
):
public static DbContextOptions<AppDbContext> TestDbContextOptions()
{
// Create a new service provider to create a new in-memory database.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Create a new options instance using an in-memory database and
// IServiceProvider that the context should resolve all of its
// services from.
var builder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb")
.UseInternalServiceProvider(serviceProvider);
return builder.Options;
}
El uso de DbContextOptions
en las pruebas unitarias de DAL permite que cada prueba se ejecute de forma atómica con una instancia de base de datos completamente nueva:
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Use the db here in the unit test.
}
Cada método de prueba de la clase DataAccessLayerTest
(UnitTests/DataAccessLayerTest.cs
) sigue un patrón Organización-Acción-Aserción similar:
- Organización: la base de datos se configura para la prueba y/o el resultado previsto se define.
- Acción: la prueba se ejecuta.
- Aserción: se realizan aserciones para determinar si el resultado de la prueba es correcto.
Por ejemplo, el método DeleteMessageAsync
es responsable de quitar un mensaje individual identificado por su Id
(src/RazorPagesTestSample/Data/AppDbContext.cs
):
public async virtual Task DeleteMessageAsync(int id)
{
var message = await Messages.FindAsync(id);
if (message != null)
{
Messages.Remove(message);
await SaveChangesAsync();
}
}
Hay dos pruebas para este método: una comprueba que el método elimina un mensaje cuando el mensaje está presente en la base de datos y la otra, que la base de datos no cambia si el Id
del mensaje que se quiere eliminar no existe. Aquí mostramos el método DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound
:
[Fact]
public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
{
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
// Act
await db.DeleteMessageAsync(recId);
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
}
}
En primer lugar, el método lleva a cabo el paso de organización, donde se prepara todo para el paso de acción. Los mensajes de inicialización se obtienen y se conservan en seedMessages
. Los mensajes de inicialización se guardan en la base de datos. El mensaje con un Id
de 1
se establece para eliminarse. Cuando el método DeleteMessageAsync
se ejecuta, los mensajes esperados deben incluir todos los mensajes, excepto el que tenga un Id
de 1
. La variable expectedMessages
representa el resultado esperado.
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
El método actúa: El método DeleteMessageAsync
se ejecuta pasando un recId
de 1
:
// Act
await db.DeleteMessageAsync(recId);
Por último, el método obtiene el elemento Messages
del contexto y lo compara con el elemento expectedMessages
, afirmando que los dos son iguales:
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
Para comparar que los dos elementos List<Message>
son iguales:
- Los mensajes se ordenan por
Id
. - Los pares de mensajes se comparan en la propiedad
Text
.
Un método de prueba similar, DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound
, comprueba el resultado de intentar eliminar un mensaje que no existe. En este caso, los mensajes esperados en la base de datos deben ser iguales a los mensajes reales después de que el método DeleteMessageAsync
se ejecute. No debe haber ningún cambio en el contenido de la base de datos:
[Fact]
public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound()
{
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Arrange
var expectedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(expectedMessages);
await db.SaveChangesAsync();
var recId = 4;
// Act
try
{
await db.DeleteMessageAsync(recId);
}
catch
{
// recId doesn't exist
}
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
}
}
Pruebas unitarias de los métodos del modelo de página
Otro conjunto de pruebas unitarias es responsable de las pruebas de los métodos del modelo de página. En la aplicación de mensajes, los modelos de página Index se encuentran en la clase IndexModel
en src/RazorPagesTestSample/Pages/Index.cshtml.cs
.
Método del modelo de página | Función |
---|---|
OnGetAsync |
Obtiene los mensajes de la DAL de la interfaz de usuario mediante el método GetMessagesAsync . |
OnPostAddMessageAsync |
Si ModelState es válido, se llama a AddMessageAsync para agregar un mensaje a la base de datos. |
OnPostDeleteAllMessagesAsync |
Llama a DeleteAllMessagesAsync para eliminar todos los mensajes de la base de datos. |
OnPostDeleteMessageAsync |
Ejecuta DeleteMessageAsync para eliminar un mensaje con el Id especificado. |
OnPostAnalyzeMessagesAsync |
Si hay uno o más mensajes en la base de datos, calcula la media de palabras por mensaje. |
Los métodos del modelo de página se comprueban con siete pruebas en la clase IndexPageTests
(tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs
). Las pruebas usan el conocido patrón Organización-Aserción-Acción. Estas pruebas se centran en lo siguiente:
- Determinar si los métodos siguen el comportamiento correcto cuando ModelState no es válido.
- Confirmar que los métodos generan el elemento IActionResult correcto.
- Comprobar que las asignaciones de valores de propiedad se realizan correctamente.
Este grupo de pruebas suele simular los métodos de DAL para generar los datos esperados en el paso de acción, en el que un método de modelo de página se ejecuta. Por ejemplo, el método GetMessagesAsync
de AppDbContext
se simula para generar una salida. Cuando un método de modelo de página ejecuta este método, la simulación devuelve el resultado. Los datos no provienen de la base de datos. Esto crea condiciones de prueba de confianza y confiables para usar la DAL en las pruebas del modelo de página.
La prueba OnGetAsync_PopulatesThePageModel_WithAListOfMessages
muestra cómo se simula el método GetMessagesAsync
para el modelo de página:
var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(
db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var pageModel = new IndexModel(mockAppDbContext.Object);
Cuando el método OnGetAsync
se ejecuta en el paso de acción, llama al método GetMessagesAsync
del modelo de página.
Paso de acción de prueba unitaria (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs
):
// Act
await pageModel.OnGetAsync();
Método OnGetAsync
del modelo de página IndexPage
(src/RazorPagesTestSample/Pages/Index.cshtml.cs
):
public async Task OnGetAsync()
{
Messages = await _db.GetMessagesAsync();
}
El método GetMessagesAsync
de la DAL no devuelve el resultado de esta llamada al método. La versión simulada del método devuelve el resultado.
En el paso Assert
, los mensajes reales (actualMessages
) se asignan desde la propiedad Messages
del modelo de página. También se realiza una comprobación de tipo cuando los mensajes se asignan. Las propiedades Text
de los mensajes esperados y reales se comparan. La prueba afirma que las dos instancias de List<Message>
contienen los mismos mensajes.
// Assert
var actualMessages = Assert.IsAssignableFrom<List<Message>>(pageModel.Messages);
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
Otras pruebas de este grupo crean objetos de modelo de página que incluyen DefaultHttpContext, ModelStateDictionary, un elemento ActionContext para establecer PageContext
, un elemento ViewDataDictionary
y un elemento PageContext
. Todos ellos resultan útiles para realizar pruebas. Por ejemplo, la aplicación de mensajes establece un error de ModelState
con AddModelError para comprobar si se devuelve un elemento PageResult válido cuando OnPostAddMessageAsync
se ejecuta:
[Fact]
public async Task OnPostAddMessageAsync_ReturnsAPageResult_WhenModelStateIsInvalid()
{
// Arrange
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var httpContext = new DefaultHttpContext();
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>());
var pageContext = new PageContext(actionContext)
{
ViewData = viewData
};
var pageModel = new IndexModel(mockAppDbContext.Object)
{
PageContext = pageContext,
TempData = tempData,
Url = new UrlHelper(actionContext)
};
pageModel.ModelState.AddModelError("Message.Text", "The Text field is required.");
// Act
var result = await pageModel.OnPostAddMessageAsync();
// Assert
Assert.IsType<PageResult>(result);
}
Recursos adicionales
- Prueba unitaria de C# en .NET Core mediante pruebas de dotnet y xUnit
- Probar la lógica del controlador en ASP.NET Core
- Prueba unitaria del código (Visual Studio)
- Pruebas de integración en ASP.NET Core
- xUnit.net
- Introducción a xUnit.net: Uso de .NET Core con la línea de comandos del SDK de .NET
- Moq
- Inicio rápido de Moq
ASP.NET Core admite pruebas unitarias de aplicaciones de Razor Pages. Las pruebas de la capa de acceso a datos (DAL) y de los modelos de página ayudan a garantizar lo siguiente:
- Las partes de una aplicación de Razor Pages funcionan de forma independiente y conjunta como una unidad durante la creación de una aplicación.
- Las clases y los métodos tienen ámbitos de responsabilidad restringidos.
- Hay documentación adicional disponible sobre cómo debe comportarse la aplicación.
- Se detectan regresiones —que son errores que salen a la luz a raíz de las actualizaciones de código— en los procesos de compilación e implementación automatizados.
En este tema se da por hecho que el usuario posee conocimientos básicos de las pruebas unitarias y las aplicaciones de Razor Pages. Si no está familiarizado con los conceptos de prueba o aplicaciones de Razor Pages, vea los siguientes temas:
- Introducción a Razor Pages en ASP.NET Core
- Tutorial: Introducción a Razor Pages en ASP.NET Core
- Prueba unitaria de C# en .NET Core mediante pruebas de dotnet y xUnit
Vea o descargue el código de ejemplo (cómo descargarlo)
El proyecto de ejemplo se compone de dos aplicaciones:
Aplicación | Carpeta del proyecto | Descripción |
---|---|---|
Aplicación de mensajes | src/RazorPagesTestSample | Permite a un usuario agregar un mensaje, eliminar un mensaje, eliminar todos los mensajes y analizar mensajes (hallar la media de palabras por mensaje). |
Probar la aplicación | tests/RazorPagesTestSample.Tests | Sirve para realizar una prueba unitaria del modelo de página Index y la DAL de la aplicación de mensajes. |
Las pruebas se pueden ejecutar con las características de prueba integradas de un IDE, como Visual Studio. Si usa Visual Studio Code o la línea de comandos, ejecute el siguiente comando en un símbolo del sistema del directorio tests/RazorPagesTestSample.Tests:
dotnet test
Organización de la aplicación de mensajes
La aplicación de mensajes es un sistema de mensajes de Razor Pages con las siguientes características:
- La página Index de la aplicación (
Pages/Index.cshtml
yPages/Index.cshtml.cs
) proporciona una interfaz de usuario y métodos de modelo de página para controlar la adición, eliminación y análisis de mensajes (hallar la media de palabras por mensaje). - La clase
Message
(Data/Message.cs
) describe un mensaje con dos propiedades:Id
(clave) yText
(mensaje). Se necesita la propiedadText
, que está limitada a 200 caracteres. - Los mensajes se almacenan en la base de datos en memoria de Entity Framework†.
- La aplicación contiene una DAL en su clase de contexto de base de datos,
AppDbContext
(Data/AppDbContext.cs
). Los métodos de DAL se marcan comovirtual
, lo que permite realizar simulaciones en ellos para usarlos en las pruebas. - Si la base de datos está vacía al inicio de una aplicación, el almacén de mensajes se inicializa con tres mensajes. Estos mensajes inicializados también se usan en las pruebas.
†En el tema de EF, Pruebas con InMemory, se explica cómo usar una base de datos en memoria con las pruebas con MSTest. En este tema se usa el marco de pruebas xUnit. Los conceptos y las implementaciones de prueba de diferentes marcos de pruebas son similares, pero no idénticos.
Aunque la aplicación de ejemplo no usa el patrón del repositorio y no es un ejemplo eficaz del patrón Unit of Work (UoW), Razor Pages admite estos patrones de desarrollo. Para más información, vea Diseño del nivel de persistencia de infraestructura y Lógica del controlador de pruebas en ASP.NET Core (el ejemplo implementa el patrón del repositorio).
Organización de la aplicación de prueba
La aplicación de prueba es una aplicación de consola dentro de la carpeta tests/RazorPagesTestSample.Tests.
Carpeta de aplicación de prueba | Descripción |
---|---|
UnitTests |
|
Utilities | Contiene el método TestDbContextOptions empleado para crear nuevas opciones de contexto de base de datos para cada prueba unitaria de DAL, de modo que la base de datos se restablezca a su condición de línea base en cada prueba. |
El marco de pruebas es xUnit. El marco de trabajo de simulación de objetos es Moq.
Pruebas unitarias de la capa de acceso a datos (DAL)
La aplicación de mensajes tiene una DAL con cuatro métodos contenidos en la clase AppDbContext
(src/RazorPagesTestSample/Data/AppDbContext.cs
). Cada método tiene una o dos pruebas unitarias en la aplicación de prueba.
Método de DAL | Función |
---|---|
GetMessagesAsync |
Obtiene un elemento List<Message> de la base de datos ordenada por la propiedad Text . |
AddMessageAsync |
Agrega un elemento Message a la base de datos. |
DeleteAllMessagesAsync |
Elimina todas las entradas Message de la base de datos. |
DeleteMessageAsync |
Elimina todas las entradas Message de la base de datos según Id . |
Las pruebas unitarias de la DAL requieren DbContextOptions al crear un elemento AppDbContext
para cada prueba. Un método para crear el elemento DbContextOptions
de cada prueba es usar un elemento DbContextOptionsBuilder:
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
using (var db = new AppDbContext(optionsBuilder.Options))
{
// Use the db here in the unit test.
}
El inconveniente de este método es que cada prueba recibe la base de datos en el estado en el que la dejó la prueba anterior. Esto puede ser problemático al intentar escribir pruebas unitarias atómicas que no interfieran entre sí. Para forzar que AppDbContext
use un nuevo contexto de base de datos en cada prueba, proporcione una instancia de DbContextOptions
que esté basada en un nuevo proveedor de servicios. La aplicación de prueba muestra cómo llevar esto a cabo con el método TestDbContextOptions
de su clase Utilities
(tests/RazorPagesTestSample.Tests/Utilities/Utilities.cs
):
public static DbContextOptions<AppDbContext> TestDbContextOptions()
{
// Create a new service provider to create a new in-memory database.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Create a new options instance using an in-memory database and
// IServiceProvider that the context should resolve all of its
// services from.
var builder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb")
.UseInternalServiceProvider(serviceProvider);
return builder.Options;
}
El uso de DbContextOptions
en las pruebas unitarias de DAL permite que cada prueba se ejecute de forma atómica con una instancia de base de datos completamente nueva:
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Use the db here in the unit test.
}
Cada método de prueba de la clase DataAccessLayerTest
(UnitTests/DataAccessLayerTest.cs
) sigue un patrón Organización-Acción-Aserción similar:
- Organización: la base de datos se configura para la prueba y/o el resultado previsto se define.
- Acción: la prueba se ejecuta.
- Aserción: se realizan aserciones para determinar si el resultado de la prueba es correcto.
Por ejemplo, el método DeleteMessageAsync
es responsable de quitar un mensaje individual identificado por su Id
(src/RazorPagesTestSample/Data/AppDbContext.cs
):
public async virtual Task DeleteMessageAsync(int id)
{
var message = await Messages.FindAsync(id);
if (message != null)
{
Messages.Remove(message);
await SaveChangesAsync();
}
}
Hay dos pruebas para este método: una comprueba que el método elimina un mensaje cuando el mensaje está presente en la base de datos y la otra, que la base de datos no cambia si el Id
del mensaje que se quiere eliminar no existe. Aquí mostramos el método DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound
:
[Fact]
public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
{
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
// Act
await db.DeleteMessageAsync(recId);
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
}
}
En primer lugar, el método lleva a cabo el paso de organización, donde se prepara todo para el paso de acción. Los mensajes de inicialización se obtienen y se conservan en seedMessages
. Los mensajes de inicialización se guardan en la base de datos. El mensaje con un Id
de 1
se establece para eliminarse. Cuando el método DeleteMessageAsync
se ejecuta, los mensajes esperados deben incluir todos los mensajes, excepto el que tenga un Id
de 1
. La variable expectedMessages
representa el resultado esperado.
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
El método actúa: El método DeleteMessageAsync
se ejecuta pasando un recId
de 1
:
// Act
await db.DeleteMessageAsync(recId);
Por último, el método obtiene el elemento Messages
del contexto y lo compara con el elemento expectedMessages
, afirmando que los dos son iguales:
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
Para comparar que los dos elementos List<Message>
son iguales:
- Los mensajes se ordenan por
Id
. - Los pares de mensajes se comparan en la propiedad
Text
.
Un método de prueba similar, DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound
, comprueba el resultado de intentar eliminar un mensaje que no existe. En este caso, los mensajes esperados en la base de datos deben ser iguales a los mensajes reales después de que el método DeleteMessageAsync
se ejecute. No debe haber ningún cambio en el contenido de la base de datos:
[Fact]
public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound()
{
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Arrange
var expectedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(expectedMessages);
await db.SaveChangesAsync();
var recId = 4;
// Act
await db.DeleteMessageAsync(recId);
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
}
}
Pruebas unitarias de los métodos del modelo de página
Otro conjunto de pruebas unitarias es responsable de las pruebas de los métodos del modelo de página. En la aplicación de mensajes, los modelos de página Index se encuentran en la clase IndexModel
en src/RazorPagesTestSample/Pages/Index.cshtml.cs
.
Método del modelo de página | Función |
---|---|
OnGetAsync |
Obtiene los mensajes de la DAL de la interfaz de usuario mediante el método GetMessagesAsync . |
OnPostAddMessageAsync |
Si ModelState es válido, se llama a AddMessageAsync para agregar un mensaje a la base de datos. |
OnPostDeleteAllMessagesAsync |
Llama a DeleteAllMessagesAsync para eliminar todos los mensajes de la base de datos. |
OnPostDeleteMessageAsync |
Ejecuta DeleteMessageAsync para eliminar un mensaje con el Id especificado. |
OnPostAnalyzeMessagesAsync |
Si hay uno o más mensajes en la base de datos, calcula la media de palabras por mensaje. |
Los métodos del modelo de página se comprueban con siete pruebas en la clase IndexPageTests
(tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs
). Las pruebas usan el conocido patrón Organización-Aserción-Acción. Estas pruebas se centran en lo siguiente:
- Determinar si los métodos siguen el comportamiento correcto cuando ModelState no es válido.
- Confirmar que los métodos generan el elemento IActionResult correcto.
- Comprobar que las asignaciones de valores de propiedad se realizan correctamente.
Este grupo de pruebas suele simular los métodos de DAL para generar los datos esperados en el paso de acción, en el que un método de modelo de página se ejecuta. Por ejemplo, el método GetMessagesAsync
de AppDbContext
se simula para generar una salida. Cuando un método de modelo de página ejecuta este método, la simulación devuelve el resultado. Los datos no provienen de la base de datos. Esto crea condiciones de prueba de confianza y confiables para usar la DAL en las pruebas del modelo de página.
La prueba OnGetAsync_PopulatesThePageModel_WithAListOfMessages
muestra cómo se simula el método GetMessagesAsync
para el modelo de página:
var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(
db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var pageModel = new IndexModel(mockAppDbContext.Object);
Cuando el método OnGetAsync
se ejecuta en el paso de acción, llama al método GetMessagesAsync
del modelo de página.
Paso de acción de prueba unitaria (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs
):
// Act
await pageModel.OnGetAsync();
Método OnGetAsync
del modelo de página IndexPage
(src/RazorPagesTestSample/Pages/Index.cshtml.cs
):
public async Task OnGetAsync()
{
Messages = await _db.GetMessagesAsync();
}
El método GetMessagesAsync
de la DAL no devuelve el resultado de esta llamada al método. La versión simulada del método devuelve el resultado.
En el paso Assert
, los mensajes reales (actualMessages
) se asignan desde la propiedad Messages
del modelo de página. También se realiza una comprobación de tipo cuando los mensajes se asignan. Las propiedades Text
de los mensajes esperados y reales se comparan. La prueba afirma que las dos instancias de List<Message>
contienen los mismos mensajes.
// Assert
var actualMessages = Assert.IsAssignableFrom<List<Message>>(pageModel.Messages);
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
Otras pruebas de este grupo crean objetos de modelo de página que incluyen DefaultHttpContext, ModelStateDictionary, un elemento ActionContext para establecer PageContext
, un elemento ViewDataDictionary
y un elemento PageContext
. Todos ellos resultan útiles para realizar pruebas. Por ejemplo, la aplicación de mensajes establece un error de ModelState
con AddModelError para comprobar si se devuelve un elemento PageResult válido cuando OnPostAddMessageAsync
se ejecuta:
[Fact]
public async Task OnPostAddMessageAsync_ReturnsAPageResult_WhenModelStateIsInvalid()
{
// Arrange
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var httpContext = new DefaultHttpContext();
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>());
var pageContext = new PageContext(actionContext)
{
ViewData = viewData
};
var pageModel = new IndexModel(mockAppDbContext.Object)
{
PageContext = pageContext,
TempData = tempData,
Url = new UrlHelper(actionContext)
};
pageModel.ModelState.AddModelError("Message.Text", "The Text field is required.");
// Act
var result = await pageModel.OnPostAddMessageAsync();
// Assert
Assert.IsType<PageResult>(result);
}
Recursos adicionales
- Prueba unitaria de C# en .NET Core mediante pruebas de dotnet y xUnit
- Probar la lógica del controlador en ASP.NET Core
- Prueba unitaria del código (Visual Studio)
- Pruebas de integración en ASP.NET Core
- xUnit.net
- Introducción a xUnit.net: Uso de .NET Core con la línea de comandos del SDK de .NET
- Moq
- Inicio rápido de Moq
- JustMockLite: marco ficticio para desarrolladores de .NET. (Microsoft no realiza su mantenimiento ni su soporte técnico. )