用于 Effort 单元测试的 Shim DbContext ctor

Shim DbContext ctor for Effort unit testing

我想拦截 var context = new MyDbContext() 到 return 不同的构造函数调用。

EFfort 的伟大之处在于它让您可以为单元测试设置一个简单的内存数据库。

var connection = Effort.DbConnectionFactory.CreateTransient();
var testContext = new MyDbContext(connection);

但是您必须将 context 注入您的存储库。

public FooRepository(MyDbContext context) { _context = context; }

是否可以只拦截 var context = new MyDbContext() ,以便 return 成为 testContext

using (var context = new MyDbContext()) {
    // this way, my code isn't polluted with a ctor just for testing
}

您有两个可能的选择。使用工厂或通过面向方面的编程(如 PostSharp)

参考这篇文章:http://www.progware.org/Blog/post/Interception-and-Interceptors-in-C-(Aspect-oriented-programming).aspx

使用 PostSharp (AOP)

PostSharp is a great tool and can achieve the most clean interception possible (meaning no changes in your classes and object generation at all even if you do not your factories for object creation and/or interfaces) but it is not a free library. Rather than creating proxies at runtime, it injects code at compile time and therefore changes your initial program in a seamless way to add method interception.
.....
The cool thing in this is that you do not change anything else in your code, so your object can be still generated using the new keyword.

使用 DI 和工厂模式

我个人更喜欢工厂模式方法,但您似乎不愿意将任何依赖项注入您的 类。

public interface IDbContextFactory<T> where T : DbContext {
    T Create();
}

public class TestDbContextFactory : IDbContextFactory<MyDbContext> {
    public MyDbContext Create() {
        var connection = Effort.DbConnectionFactory.CreateTransient();
        var testContext = new MyDbContext(connection);
        return testContext;
    }
}

public class FooRepository {
    MyDbContext _context;
    public FooRepository(IDbContextFactory<MyDbContext> factory) { 
        _context = factory.Create(); 
    }
}

(编辑:我刚刚意识到这实际上并没有返回另一个 ctor 调用。正在处理它。)

想通了。如果你知道怎么做就足够简单了:

        [TestMethod]
        public void Should_have_a_name_like_this()
        {
            // Arrange
            var connection = Effort.DbConnectionFactory.CreateTransient();
            ShimSolrDbContext.Constructor = context => new SolrDbContext(connection);

            // Act


            // Assert

        }

和往常一样,EFfort 在 DbContext 中需要这个构造函数 class:

public class SomeDbContext
{
    public SomeDbContext() : base("name=Prod")
    {
    }

    // EFfort unit testing ctor
    public SomeDbContext(DbConnection connection) : base(connection, contextOwnsConnection: true) {
        Database.SetInitializer<SolrDbContext>(null);
    }
}

但这意味着 repo 没有意识到特殊的瞬态连接:

public class SomeRepository
{
    public void SomeMethodName()
    {
        using (var context = new SomeDbContext())
        {
            // self-contained in repository, no special params
            // and still calls the special test constructor
        }
    }
}