如何在没有 public 构造函数的情况下为密封的 class 创建夹具?

How to create fixture for sealed class with no public constructor?

我已经密封了一个没有构造函数的class,这是我使用 SDK 引用到我的项目。我想为 class 创建夹具数据来编写测试,但 AutoFixture 给出了如下期望。

AutoFixture was unable to create an instance from SealedTypeclass, most likely because it has no public constructor, is an abstract or non-public type.

请找到我正在为其创建固定装置的以下代码示例。

public sealed class TokenCacheItem
{
    public string Authority { get; }
    public string ClientId { get; }
    public DateTimeOffset ExpiresOn { get; }
    public string FamilyName { get; }
    public string GivenName { get; }
    public string IdentityProvider { get; }
    public string TenantId { get; }
}

使用上面密封的 class 我是通过 SDK 引用的,我正在尝试创建夹具数据。我收到以下错误消息。

留言: AutoFixture.ObjectCreationExceptionWithPath : AutoFixture 无法从 *** 创建实例,很可能是因为它没有 public 构造函数,是抽象或非 public 类型。

任何通用解决方案?

鉴于它是密封的并报告它没有 public 构造函数,您将必须提供一种创建实例的方法,并且它可能是基于反射的解决方案。只读属性也增加了 AutoFixture 的复杂性。

没有看到构造函数,也没有看到只读属性是否有任何支持字段,我将对以下工作示例做出一些假设。

创建可以创建 TokenCacheItem 并设置其属性的东西:

public class MutableTokenCacheItem
{
    private TokenCacheItem _tokenCacheItem;

    public string Authority { get => _tokenCacheItem.Authority; set => SetPropertyValue(x => x.Authority, value); }
    public string ClientId { get => _tokenCacheItem.ClientId; set => SetPropertyValue(x => x.ClientId, value); }
    public DateTimeOffset ExpiresOn { get => _tokenCacheItem.ExpiresOn; set => SetPropertyValue(x => x.ExpiresOn, value); }
    public string FamilyName { get => _tokenCacheItem.FamilyName; set => SetPropertyValue(x => x.FamilyName, value); }
    public string GivenName { get => _tokenCacheItem.GivenName; set => SetPropertyValue(x => x.GivenName, value); }
    public string IdentityProvider { get => _tokenCacheItem.IdentityProvider; set => SetPropertyValue(x => x.IdentityProvider, value); }
    public string TenantId { get => _tokenCacheItem.TenantId; set => SetPropertyValue(x => x.TenantId, value); }

    public MutableTokenCacheItem()
    {
        var ctor = typeof(TokenCacheItem).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).Single();
        _tokenCacheItem = (TokenCacheItem)ctor.Invoke(null);
    }

    private void SetPropertyValue<P>(Expression<Func<TokenCacheItem, P>> expression, object value)
    {
        var body = expression.Body as MemberExpression;
        var backingField = typeof(TokenCacheItem).GetRuntimeFields().Where(a => Regex.IsMatch(a.Name, $"\A<{body.Member.Name}>k__BackingField\Z")).Single();
        backingField.SetValue(_tokenCacheItem, value);
    }

    public TokenCacheItem AsImmutableTokenCacheItem()
    {
        return _tokenCacheItem;
    }
}

创建一个样本构建器以将其很好地固定到 AutoFixture 中:

public class TokenCacheItemSpecimenBuilder : ISpecimenBuilder
{
    public object Create(object request, ISpecimenContext context)
    {
        var t = request as Type;
        if (typeof(TokenCacheItem).Equals(t))
        {
            var mutableTokenCacheItem = context.Create<MutableTokenCacheItem>();
            return mutableTokenCacheItem.AsImmutableTokenCacheItem();
        }

        return new NoSpecimen();
    }
}

添加自定义,然后开始:

var fixture = new Fixture();
fixture.Customizations.Add(new TokenCacheItemSpecimenBuilder());
fixture.Create<TokenCacheItem>();

工作 LINQPad 示例:

void Main()
{
    var fixture = new Fixture();
    fixture.Customizations.Add(new TokenCacheItemSpecimenBuilder());
    Console.Write(fixture.Create<TokenCacheItem>());
}

public class TokenCacheItemSpecimenBuilder : ISpecimenBuilder
{
    public object Create(object request, ISpecimenContext context)
    {
        var t = request as Type;
        if (typeof(TokenCacheItem).Equals(t))
        {
            var mutableTokenCacheItem = context.Create<MutableTokenCacheItem>();
            return mutableTokenCacheItem.AsImmutableTokenCacheItem();
        }

        return new NoSpecimen();
    }
}

public class MutableTokenCacheItem
{
    private TokenCacheItem _tokenCacheItem;

    public string Authority { get => _tokenCacheItem.Authority; set => SetPropertyValue(x => x.Authority, value); }
    public string ClientId { get => _tokenCacheItem.ClientId; set => SetPropertyValue(x => x.ClientId, value); }
    public DateTimeOffset ExpiresOn { get => _tokenCacheItem.ExpiresOn; set => SetPropertyValue(x => x.ExpiresOn, value); }
    public string FamilyName { get => _tokenCacheItem.FamilyName; set => SetPropertyValue(x => x.FamilyName, value); }
    public string GivenName { get => _tokenCacheItem.GivenName; set => SetPropertyValue(x => x.GivenName, value); }
    public string IdentityProvider { get => _tokenCacheItem.IdentityProvider; set => SetPropertyValue(x => x.IdentityProvider, value); }
    public string TenantId { get => _tokenCacheItem.TenantId; set => SetPropertyValue(x => x.TenantId, value); }

    public MutableTokenCacheItem()
    {
        var ctor = typeof(TokenCacheItem).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).Single();
        _tokenCacheItem = (TokenCacheItem)ctor.Invoke(null);
    }

    private void SetPropertyValue<P>(Expression<Func<TokenCacheItem, P>> expression, object value)
    {
        var body = expression.Body as MemberExpression;
        var backingField = typeof(TokenCacheItem).GetRuntimeFields().Where(a => Regex.IsMatch(a.Name, $"\A<{body.Member.Name}>k__BackingField\Z")).Single();
        backingField.SetValue(_tokenCacheItem, value);
    }

    public TokenCacheItem AsImmutableTokenCacheItem()
    {
        return _tokenCacheItem;
    }
}

public sealed class TokenCacheItem
{
    public string Authority { get; }
    public string ClientId { get; }
    public DateTimeOffset ExpiresOn { get; }
    public string FamilyName { get; }
    public string GivenName { get; }
    public string IdentityProvider { get; }
    public string TenantId { get; }

    private TokenCacheItem() { }
}

结果:

YMMV 取决于实际的 TokenCacheItem 实施,虽然它可能相距不远。