简体   繁体   中英

Shim DbContext ctor for Effort unit testing

I'd like to intercept var context = new MyDbContext() to return a different constructor call instead.

The great thing about EFfort is that it let's you set up an easy in-memory database for unit testing.

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

But then you'd have to inject that context into your repository.

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

Is it possible to just intercept var context = new MyDbContext() , so that it returns the testContext ?

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

You have two possible options. Using factories or via Aspect oriented programming (like PostSharp)

referencing this article: http://www.progware.org/Blog/post/Interception-and-Interceptors-in-C-(Aspect-oriented-programming).aspx

Using 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.

Using DI and Factory-pattern

I personally prefer the factory-pattern approach, but you seem apposed to having to inject any dependencies into your classes.

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(); 
    }
}

(edit: I just realized this isn't actually returning the other ctor call. working on it.)

Figured it out. Simple enough if you know how to do it:

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

            // Act


            // Assert

        }

And as usual, EFfort requires this constructor in the 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);
    }
}

But it means the repo is blissfully unaware of the special Transient connection:

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
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM