Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to freeze dependencies #28

Open
jacob7395 opened this issue Sep 29, 2024 · 0 comments
Open

Ability to freeze dependencies #28

jacob7395 opened this issue Sep 29, 2024 · 0 comments

Comments

@jacob7395
Copy link

jacob7395 commented Sep 29, 2024

This is a continuation of the discutions in AutoBogus PR#263.

NSubstituteBinder.CreateInstance being abstract dose allow for some kind of caching wrapper to be implemented independently.

It can be hard to explain in an abstract way but this article shows why it can be useful to have this functionality, using AutoFixture and Moq. The idea being that by injecting the same instance into the test it cuts down on lines of copy and paste code. So the start of the test doesn't have the same boiler plate of setting up the mock classes. Also with the current setup it may not be possible to alter the mock after the class has been constructed, if the mock is private/protected.

I find it hard to make up an example but hopefully, the code bellow will illustrate how this can be useful. Especially if the interface you need to mock is several layers of instantiation deep A -makes-> B -has interface-> IC, that you are trying to test.

public class MockClass(IRepository repository)
{
    public IRepository Repository => repository;//only exposed for tests
    public async Task<Model> GetModelWithId(Guid id)
    {
        return await repository.Get(id);
    }
}

public interface IRepository
{
    Task<Model> Get(Guid id);
}

public record Model(Guid Id);

public class TestExample
{
    // This would be the normal way of setting up test using NSubstitute
    [Fact]
    public async Task GetModelWithId_ShouldCallRepository_WithCorrectId()
    {
        // arrange

        // lots of lines to just make the sub and configure it, this gets annoying when you have 3+ dependencies and need to alter for every test
        var repository = Substitute.For<IRepository>();
        var expectedModel = new Model(Guid.NewGuid());
        var id = Guid.NewGuid();
        repository.Get(id).Returns(expectedModel);

        // then make the test class
        var testClass = new MockClass(repository);

        // act
        var result = await testClass.GetModelWithId(id);

        // assert
        result.Should().Be(expectedModel);
        await repository.Received().Get(id);
    }

    [Fact]
    // With the current AutoBogus this isn't possible to test, the Repository has to be public, so the test can access it.
    // In most cases I don't think this would be the correct design, since it will be altered just to test the functinality.
    public async Task GetModelWithId_ShouldCallRepository_WithCorrectId_UsingAutoFaker()
    {
        // arrange
        var testClass = new AutoFaker().Generate<MockClass>();

        // if the repository was public could so this
        var testModel = new Model(Guid.NewGuid());
        testClass.Repository.Get(testModel.Id).Returns(testModel);

        // act
        var result = await testClass.GetModelWithId(testModel.Id);

        // assert
        result.Should().Be(testModel);
        await testClass.Repository.Received().Get(testModel.Id);
    }

    // If the interface can be frozen then this becomes something like
    // With the current AutoBogus this isn't possible to test
    [Fact]
    public async Task GetModelWithId_ShouldCallRepository_WithCorrectId_UsingAutoFakerWithAFrozen()
    {
        // arrange
        var faker = new AutoFaker();

        var repository = faker.Generate<IRepository>();// with an attribute like - frozen: true
        var testModel = faker.Generate<Model>();
        repository.Get(testModel.Id).Returns(testModel);

        var testClass = faker.Generate<MockClass>();

        // act
        var result = await testClass.GetModelWithId(testModel.Id);

        // assert
        result.Should().Be(testModel);
        await repository.Received().Get(testModel.Id);
    }

    // This becomes most powerful when you combine it with an attribute to autopopulate the values
    [Fact]
    public async Task GetModelWithId_ShouldCallRepository_WithCorrectId(
            [Frozen] IRepository repository, Model testModel, MockClass testClass)
    {
        // arrange
        repository.Get(testModel.Id).Returns(testModel);

        // act
        var result = await testClass.GetModelWithId(testModel.Id);

        // assert
        result.Should().Be(testModel);
        await repository.Received().Get(testModel.Id);
    }
}

Also want to say, there is no expectation for you to take the library this way. These are only suggestions from how I want to use testing libraries. Ultimately I can just fork this and Bogus to make the modifications I want to see. I am happy to help extend this library though if you see the value of this approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant