Fact vs Theory
[Fact] son tests que se ejecutan siempre con los mismos valores fijos
[Fact]
public void Sum_TwoNumbers_ReturnsCorrectResult()
{
// Act
var result = 1 + 2;
// Assert
result.Should().Be(3);
}
[Theory] son tests parametrizados que se ejecutan múltiples veces con datos diferentes. Requieren de otro modificador que provea los valores
[Theory]
[InlineData(1, 2, 3)]
[InlineData(0, 0, 0)]
[InlineData(-1, 1, 0)]
public void Sum_TwoNumbers_ReturnsCorrectResult(int a, int b, int expected)
{
// Act
var result = a + b;
// Assert
result.Should().Be(expected);
}
Data providers Theory
[InlineData]
Es el modificador más simple. Datos directamente en el atributo.
mismo ejemplo de arriba
[Theory]
[InlineData(1, 2, 3)]
[InlineData(0, 0, 0)]
[InlineData(-1, 1, 0)]
public void Sum_TwoNumbers_ReturnsCorrectResult(int a, int b, int expected)
{
// Act
var result = a + b;
// Assert
result.Should().Be(expected);
}
[MemberData]
Datos desde una propiedad o método estático. Útil para datos complejos o reutilizables entre test classes.
public static IEnumerable<object[]> EntityData =>
[
[new Entity("contact") { Id = Guid.NewGuid() }, "contact"],
[new Entity("user") { Id = Guid.NewGuid() }, "user"],
];
[Theory]
[MemberData(nameof(EntityData))]
public void Delete_WhenCalled_InvokesServiceClientDelete(Entity entity, string entityName)
{
// Act
adapter.Delete(entityName, entity.Id);
// Assert
serviceClientMock.Verify(x => x.Delete(entityName, entity.Id), Times.Once);
}
[ClassData]
Aporta la misma flexibilidad que [MemberData] pero aporta los datos desde una clase separada que implementa IEnumerable<object[]>. Ideal para datasets grandes o compartidos.
public class QueryExpressionData : IEnumerable<object[]>
{
public static IEnumerator<object[]> GetEnumerator()
{
yield return [new QueryExpression("contact"), new EntityCollection([new Entity("contact")])];
yield return [new QueryExpression("user"), new EntityCollection([new Entity("user")])];
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
[Theory]
[ClassData(typeof(QueryExpressionData))]
public void RetrieveMultiple_WhenCalled_ReturnsExpectedCollection(QueryExpression query, EntityCollection expected)
{
// Arrange
serviceClientMock.Setup(x => x.RetrieveMultiple(query)).Returns(expected);
// Act
var result = adapter.RetrieveMultiple(query);
// Assert
result.Should().BeSameAs(expected);
}
IClassFixture<T>
IClassFixture<T> is an interface that enables shared setup across all tests inside a class.
It’s used for setup-intensive classes so they are setup only once for one (or multiple) test classes, instead of before each test.
first we declare the Fixture
public class DatabaseFixture
{
public readonly IDatabase db { get; }
public DatabaseFixture()
{
db = SetupDatabase();
}
private IDatabase SetupDatabase()
{
// do whatever to setup the database and fill it with data
}
}
then we use it in a test class
public class DatabaseTests : IClassFixture<DatabaseFixture>
{
private readonly IDatabase _db;
// xUnit injects the fixture through the constructor
public DatabaseTests(DatabaseFixture dbFixture)
{
_db = dbFixture.db;
}
}