Emil Terman
Emil Terman

Reputation: 526

Generating copies with Autofixture

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

Answers (1)

Aage
Aage

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 xUnits 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

Related Questions