C# 替代自动夹具自定义设置

C# 替代自动夹具自定义设置,c#,unit-testing,autofixture,nsubstitute,C#,Unit Testing,Autofixture,Nsubstitute,我在一个类的构造函数中注入了几个服务。我将Autofixture与xUnit.net和NSubstitute一起使用,并创建了一个属性来设置全局自定义 public class AutoDbDataAttribute : AutoDataAttribute { public AutoDbDataAttribute() : base(() => new Fixture().Customize(new AutoNSubstituteCustomization())) {

我在一个类的构造函数中注入了几个服务。我将Autofixture与xUnit.net和NSubstitute一起使用,并创建了一个属性来设置全局自定义

public class AutoDbDataAttribute : AutoDataAttribute
{
    public AutoDbDataAttribute() : base(() => new Fixture().Customize(new AutoNSubstituteCustomization()))
    {

    }

    public AutoDbDataAttribute(Type customizationType) : base(() =>
    {
        var customization = Activator.CreateInstance(customizationType) as ICustomization;

        var fixture = new Fixture();
        fixture.Customize(new AutoNSubstituteCustomization());
        fixture.Customize(customization);

        return fixture;
    })
    {

    }
}
我还有一个定制类,它为同一类中的测试方法设置公共定制

public class RevenueProviderCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Register<IRevenueContextService>(() =>
        {
            var contextService = Substitute.For<IRevenueContextService>();
            contextService.GetContext().Returns(fixture.Create<RevenueContext>());
            return contextService;
        });

        fixture.Register<ICompanyService>(() =>
        {
            var companyService = Substitute.For<ICompanyService>();
            companyService.Get(Arg.Any<Guid>()).Returns(fixture.Create<Company>());
            return companyService;
        });
    }
}
但这不起作用。来自
RevenueProviderCustomization
RevenueContext
仍在使用

有人知道我如何覆盖服务的返回吗?我不想在测试中一个接一个地设置夹具,所以我希望能够创建一个“常规设置”,并根据测试用例进行必要的修改

更新1

试着从马克那里得到答案,我把测试改为

[Theory]
    [AutoDbData(typeof(RevenueProviderCustomization))]
    public void ShouldReturnCompanyRevenue([Frozen]IRevenueContextService contextService, [Frozen]Company company, RevenueProvider sut, RevenueContext context)
    {
        context.DepartmentId = null;
        contextService.GetContext().Returns(context);

        sut.GetRevenue().Should().Be(company.Revenue);
    }
问题是因为RevenueProvider构造函数中调用了RevenueContext。所以我对DepartmentId的修改发生在电话打完之后

public RevenueProvider(IRevenueContextService contextService, ICompanyService companyService)
    {
        _contextService = contextService;
        _companyService = companyService;

        _company = GetCompany();
    }

    public double GetRevenue()
    {
        if (_hasDepartmentContext)
            return _company.Departments.Single(d => d.Id == _departmentId).Revenue;
        else
            return _company.Revenue;
    }

    private Company GetCompany()
    {
        RevenueContext context = _contextService.GetContext();

        if (context.DepartmentId.HasValue)
        {
            _hasDepartmentContext = true;
            _departmentId = context.DepartmentId.Value;
        }

        return _companyService.Get(context.CompanyId);
    }

假设
RevenueProvider
基本上如下所示:

[Theory]
[AutoDbData(typeof(RevenueProviderCustomization))]
public void ShouldReturnCompanyRevenue(RevenueProvider sut, Company company, [Frozen]IRevenueContextService contextService)
{
    var fixture = new Fixture();
    RevenueContext context = fixture.Build<RevenueContext>().With(c => c.DepartmentId, null).Create();
    contextService.GetContext().Returns(context);

    sut.GetRevenue().Should().Be(company.Revenue);
}
public class RevenueProvider
{
    private readonly ICompanyService companySvc;

    public RevenueProvider(ICompanyService companySvc)
    {
        this.companySvc = companySvc;
    }

    public object GetRevenue()
    {
        var company = this.companySvc.Get(Guid.Empty);
        return company.Revenue;
    }
}
然后通过以下测试:

[Theory]
[AutoDbData(typeof(RevenueProviderCustomization))]
public void ShouldReturnCompanyRevenue(
    [Frozen]ICompanyService companySvc,
    RevenueProvider sut,
    Company company)
{
    companySvc.Get(Arg.Any<Guid>()).Returns(company);
    var actual = sut.GetRevenue();
    Assert.Equal(company.Revenue, actual);
}
[理论]
[自动数据库数据(类型(RevenueProviderCustomization))]
公共无效应返回公司公平(
[冻结]我公司服务公司VC,
收入提供者sut,
(公司)
{
companySvc.Get(Arg.Any()).Returns(company);
var实际值=sut.GetRevenue();
断言。相等(公司收入、实际);
}
这个场景正是
[freezed]
属性设计用来处理的。AutoFixture定义的各种属性按参数的顺序应用。这是出于设计,因为它使您能够在冻结类型之前从参数列表中提取一些值


在OP中,
[freezed]
仅在
sut
之后应用,这就是模拟配置不适用于sut的原因。

什么是“它不工作”?如果您显示了RevenueProvider,这将很有帮助,这样我们就可以了解您要测试的内容。基本上,RevenueProvider应该返回公司或部门的收入。取决于IRevenueContextService返回的RevenueContext。如果RevenueContext具有部门ID,则应返回该部门的收入。如果RevenueContext部门ID为空,则应返回公司的收入。这就是为什么在某些情况下我需要修改模拟IRevenueContextService返回的RevenueContext对象。因为我需要DepartmentId为Null,不幸的是仍然不起作用,我将IRevenueContextService更改为sut之前,但返回的RevenueContext仍然包含DepartmentId,请参阅文章中的更新1。问题是因为对RevenueContext的调用是作为构造函数的一部分完成的。因此,对DepartmentId的修改是在之后进行的。@AdeliaBenalius我不确定您对此能做多少,但是@AdeliaBenalius或者更确切地说,我想不出一种方法来解决
[AutoData]
的问题,但仍然在测试本身中启用事后配置。我认为你要么必须改变构造函数,使它不做急迫的工作,要么必须以更迫切的方式编写测试。是的,我想我必须这样做。谢谢你的帮助,马克!