Reputation: 526
I'm using Autofixture for my unit tests, with auto-generated data.
To test a simple Controller endpoint (Get employee by Id), I'm doing something similar to this:
[Theory, AutoData]
public void GetEmployeeById_ValidId_ReturnsExpectedModel(
EmployeeModel expectedEmployee,
[Frozen] Mock<IEmployeeService> employeeServiceMock,
EmployeesController sut)
{
employeeServiceMock
.Setup(x => x.GetEmployeeById(42))
.Returns(expectedEmployee);
var actual = sut.GetEmployeeById(42);
actual.As<OkObjectResult>().Value.As<EmployeeModel>()
.Should().BeEquivalentTo(expectedEmployee);
}
And the controller:
[HttpGet("{id:int}")]
public IActionResult GetEmployeeById(int id)
{
var employee = employeeService.GetEmployeeById(id);
if (employee == null)
return NotFound("Employee not found");
return Ok(employee);
}
In this unit test, expectedEmployee
is auto-generated with 'random' data. The sut (System Under Test) is configured to be generated with all its required dependencies (one of them is IEmplyeeService
).
The problem with this unit test is that, if I change the employee before returning it from the controller, the test would still pass (because it's referencing the same object):
employee.SomeInternalModel.FooProperty = "Foo";
return Ok(employee);
So, I consider the above unit test to be bad.
To make the unit test fail in this scenario, I need to pass a separate object: deep copy of the EmployeeModel
:
employeeServiceMock
.Setup(x => x.GetEmployeeById(42))
.Returns(expectedEmployee.DeepCopy());
I don't have time and resources to write deep copy methods for all my models.
How to easily auto-generate identical models? I thought about seed-ing the AutoFixture
but it doesn't seem to support this feature.
Do you have any elegant suggestions?
Upvotes: 3
Views: 2316
Reputation: 6252
I think you need to ask the question what are you testing? In your testcase you are only testing whether the SUT
returns the employee
returned by the service
; IMO it doesn't matter whether it is the same instance. Updating a property shouldn't break this test.
You touch upon a wider problem though, in other cases you really want to compare your expected
with an actual
by structural equality
in which case you could (using xUnit
s MemberData
for instance) use a builder
which generates two instances when you call it twice:
var employee = new EmployeeModelBuilder().Build();
Such a builder could be enhanced with With()
methods:
var employee = new EmployeeModelBuilder().With(name: "John").Build();
Or you could just inline the creation of these objects using new EmployeeModel {}
.
Structural equality means you need an object which overrides Equality
members or use an IEqualityComparer<>
in your asserts.
Update
If you do not want to use custom builders (like you say) you can instruct AutoFixture
to generate objects with specific properties set with values. If you then ask it to create an instance twice (once for your expected
and once for the instance returned by the service injected into your SUT
) you can compare the expected
with the actual
in your Assert
phase.
[Fact]
public void Sut_ReturnsEmployee_FromService()
{
var fixture = new Fixture();
fixture.Customize<EmployeeModel>(e => e.With(x => x.Name, "Foo"));
var expected = fixture.Create<EmployeeModel>();
var foundEmployee = fixture.Create<EmployeeModel>();
var employeeServiceMock = new Mock<IEmployeeService>();
employeeServiceMock.Setup(f => f.GetEmployeeById(42)).Returns(foundEmployee);
var sut = new EmployeeController(employeeServiceMock.Object);
var actual = sut.GetEmployeeById(42);
Assert.Equal(expected.Name, actual.Name);
}
Here I've used a [Fact]
and the assertion compares two specific properties for equality, but when you compare structural equality you can just compare the objects themselves (like mentioned above).
Now you can verify that your SUT
returns the expected instance without tampering and without using two references to the same instance.
Upvotes: 1