模拟通用存储库工厂方法

时间:2017-08-21 13:02:40

标签: c# unit-testing mocking

我已经对我们的存储库工厂进行了重构,使其更通用,现在创建存储库的方法看起来像这样:

public TRepository CreateRepository<TRepository>(params object[] parameters)
        where TRepository : class
{
    if (_serviceProvider == null)
        throw new ArgumentNullException(nameof(_serviceProvider));

    return ActivatorUtilities.CreateInstance<TRepository>(_serviceProvider, parameters);
}

在我的生产代码中,我正在使用它,它就像魅力一样:

_concreteRepo = repoFactory.CreateRepository<ConcreteRepo>();

但是当我试图重构单元测试时,我在设置工厂时遇到了困难,这就是我这样做的方法,但它不起作用。

public class Tests 
{
    // Since I am using Moq I can't mock anything but abstract types thus having problems with type conversion in set up.
    protected readonly Mock<IConcreteRepository> _concreteRepositoryMock = new Mock<IConcreteRepository>();
    protected readonly Mock<IRepositoryFactory> _factoryMock = new Mock<IRepositoryFactory>();

    [SetUp]
    public void SetUp()
    {
        // If I don't cast concreteRepositoryMock compiler complains that cannot convert from abstract to concrete repository.
        // If I cast it fails and returns null.
         _factoryMock.Setup(f => f.CreateRepository<ConcreteRepository>())
            .Returns(_concreteRepositoryMock.Object as ConcreteRepository);
    }
}

任何想法如何解决它?好像我的CreateRepository方法正在返回具体类型,但是模仿我无法模拟我的具体存储库。我也无法将抽象类型传递给CreateRepository,因为CreateInstance需要具体类型。

2 个答案:

答案 0 :(得分:1)

我认为你的问题是你期望Mock对象:

protected readonly Mock<IConcreteRepository> _concreteRepositoryMock = new Mock<IConcreteRepository>();

成为ConcreteRepository的实例,这是错误的假设。

_concreteRepositoryMock.Object is IConcreteRepository

时应该说“TRUE”
_concreteRepositoryMock.Object is ConcreteRepository

这应该说“FALSE”。

您必须切换PROD代码以满足抽象(IConcreteRepository)或模拟最终类

protected readonly Mock<ConcreteRepository> _concreteRepositoryMock = new Mock<ConcreteRepository>();

在你的模拟中(请注意,模拟具体类通常并不容易 - 你需要所有模拟的方法都是虚拟的等等。)。

答案 1 :(得分:0)

实际上我找到了解决方法,我现在比较开心,但如果出现任何其他建议,我很乐意听到。

问题是我的CreateRepository返回具体实现,而它必须返回抽象,因为我的所有模拟都是抽象(因为Moq只模拟抽象)。因此,我修改了我的方法如下:

TInterface CreateRepository<TRepository, TInterface>(params object[] parameters)
    where TRepository : class, TInterface where TInterface : class;

这保证了我的设置方法的编译时安全性以及之后的成功。我觉得它有点麻烦,但我现在可以忍受它。

注意:您可以随时在测试程序集的内部类中执行扩展方法,并复制并粘贴生产方法中的代码,这样您就不会污染生产代码,但是,如果有人试图改变生产方法而不改变测试方法,这可能会有问题,这就是为什么我选择&#34;污染&#34;我的生产代码是为了安全起见。