繁体   English   中英

如何在EF中模拟和单元测试存储过程

[英]How to mock and unit test Stored Procedures in EF

我正在使用通用存储库模式Repository<TEntity> ,其中存储库通过上下文访问实体。 然后我有一个服务层接受构造函数中的上下文。 现在,我可以在服务中拥有多个存储库,通过相同的上下文访问实体。 很标准。 这非常适合映射到实体的表/视图,但我无法对通过存储过程传递的数据进行单元测试。

这是我目前的设置:

IDbContext:

public interface IDbContext : IDisposable
{
    IDbSet<T> Set<T>() where T : class;

    DbEntityEntry<T> Entry<T>(T entity) where T : class;

    void SetModified(object entity);

    int SaveChanges();

    // Added to be able to execute stored procedures
    System.Data.Entity.Database Database { get; }
}

语境:

public class AppDataContext : DbContext, IDbContext
{
    public AppDataContext()
        : base("Name=CONNECTIONSTRING")
    {
        base.Configuration.ProxyCreationEnabled = false;
    }

    public new IDbSet<T> Set<T>() where T : class
    {
        return base.Set<T>();
    }


    public void SetModified(object entity)
    {
        Entry(entity).State = EntityState.Modified;
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new BookingMap());
    }

    // Added to be able to execute stored procedures
    System.Data.Entity.Database Database { get { return base.Database; } }
}

通用存储库:

public class Repository<T> : IRepository<T> where T : class
{
    private readonly IDbContext context;

    public Repository(IDbContext context)
    {
        this.context = context;
    }

    public IQueryable<T> GetAll()
    {
        return this.context.Set<T>().AsQueryable();
    }

    public void Add(T entity)
    {
        this.context.Set<T>().Add(entity);
    }

    public void Delete(T entity)
    {
        this.context.Set<T>().Remove(entity);
    }

    public void DeleteAll(IEnumerable<T> entities)
    {
        foreach (var e in entities.ToList())
        {
            this.context.Set<T>().Remove(e);
        }
    }

    public void Update(T entity)
    {
        this.context.Set<T>().Attach(entity);
        this.context.SetModified(entity);
    }

    public void SaveChanges()
    {
        this.context.SaveChanges();
    }

    public void Dispose()
    {
        if (this.context != null)
        {
            this.context.Dispose();
        }
    }
}

服务:

public class BookingService
{
    IDbContext _context;

    IRepository<Booking> _bookingRepository;

    public BookingService(IDbContext context)
    {
        _context = context;

        _bookingRepository = new Repository<Booking>(context);
    }

    public IEnumerable<Booking> GetAllBookingsForName(string name)
    {
        return (from b in _bookingRepository.GetAll()
                where b.Name == name
                select b);
    }
}

测试:

[TestClass]
public class BookingServiceTest
{
    [TestMethod]
    public void Test_Get_All_Bookings_For_Name()
    {
        var mock = new Mock<IDbContext>();
        mock.Setup(x => x.Set<Booking>())
            .Returns(new FakeDbSet<Booking>
            {
                new Booking { Name = "Foo" },
                new Booking { Name = "Bar" }
            });

        BookingService _bookingService = new BookingService(mock.Object);

        var bookings = _bookingService.GetAllBookingsForName(name);

        Assert.AreEqual(2, bookings.Count(), "Booking count is not correct");
    }
}

这非常适合映射到实体的表/视图,但我无法对通过存储过程传递的数据进行单元测试。

我在互联网上查找并找到了DbContext.Database属性,我能够使用.SqlQuery()函数执行存储过程并将它们映射到实体类型。

这是我添加到Repository<T>类的内容:

public IEnumerable<T> SqlQuery(string storedProc, params object[] paramList)
{
    return this.context.Database.SqlQuery<T>(storedProc, paramList);
}

并在我的服务类中调用.SqlQuery()函数:

public IEnumerable<Booking> GetAllBookings(string name)
{
    return _bookingRepository.SqlQuery("EXEC GetAllBookings @name = {0}", name);
}

这很好用(我能得到一些数据),但我的问题是如何模拟和单元测试呢?

我刚刚遇到了这样做的需要,我的谷歌搜索让我想到了这个问题。 我不喜欢Sriram Sakthivel的答案,当我已经有一个就位时,我不想引入另一个抽象:

我已经有了一个我从DbContext提取的接口,并在一个test double中实现

我只是将int ExecuteSqlCommand(string sql, params object[] parameters)到我的界面,在实际的上下文中我实现了它:

public int ExecuteSqlCommand(string sql, params object[] parameters)
{
    return Database.ExecuteSqlCommand(sql, parameters);
}

这显然只是委托实际的EF Database属性来完成工作。

在我的测试中,我实现了这样:

public int ExecuteSqlCommand(string sql, params object[] parameters)
{
    return 0;
}

哪个没有真正做任何事情,这就是重点:你不是单元测试实际的存储过程,你只需要一种方法让它返回一些有用的东西。

我想在某些时候我可能需要它在单元测试中返回0以外的东西,此时我可能会引入类似Func<int> executeSqlCommandResultFactory来测试双构造函数以便我可以控制它,但是目前YAGNI适用。

你可以抽象出Database的一些接口财产说IDatabaseSqlQuery方法。

interface IDatabase
{
    public IEnumerable<T> SqlQuery<T>(string sql, params Object[] parameters);
}

class DatabaseWrapper : IDatabase
{
    private readonly Database database;
    public DatabaseWrapper(Database database)
    {
        this.database = database;
    }

    public IEnumerable<T> SqlQuery<T>(string sql, params Object[] parameters)
    {
        return database.SqlQuery<T>(storedProc, paramList);
    }
}

修改您的IDbContext接口使用IDatabase ,而不是具体的实例,使我们可以嘲笑它。

public interface IDbContext : IDisposable
{
    ...

    // Added to be able to execute stored procedures
    IDatabase Database { get; }
}

并以这种方式实现

public class AppDataContext : DbContext, IDbContext
{
    private readonly IDatabase database;
    public AppDataContext()
        : base("Name=CONNECTIONSTRING")
    {
        base.Configuration.ProxyCreationEnabled = false;
        this.database = new DatabaseWrapper(base.Database);
    }
    ...

    // Added to be able to execute stored procedures
    IDatabase Database { get { return database; } }
}

此时我相信您知道如何模拟IDatabase以返回测试数据。

我意识到这是一个古老的问题,但对于任何有类似问题的人来说,这是我的看法。

为什么不使用AutoFixture来创建通常从存储过程返回的数据对象并模拟存储库以返回它?

public class FooBar
{
    private Fixture fixture;
    private Mock<BookingRepository> bookingRepository; 

    public FooBar()
    {
        fixture = new Fixture();
        bookingRepository= new Mock<BookingRepository>();
    }

    public void TestInitialize()
    {
        var sprocObject = fixture.Create<DbObject>();

        bookingRepository.Setup(x => x.GetAllBookings(It.IsAny<string>())).Returns(sprocObject);
    }

    // Unit tests
}

正如Gert Arnold所说,如果要测试实际的存储过程,则应该进行集成测试。 如果您正在测试服务/存储库逻辑,则只需返回一些数据。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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