2014-09-24 14 views
13

Tôi đã có thể giả lập DbSet từ khung thực thể với Moq bằng cách sử dụng link này.Làm thế nào để Moq Entity Framework Các cuộc gọi SqlQuery

Tuy nhiên, bây giờ tôi muốn biết làm cách nào tôi có thể thực hiện cuộc gọi đến SqlQuery. Không chắc chắn nếu điều này là có thể hoặc làm thế nào khi nó dựa trên bối cảnh db giả lập biết những gì "truy vấn" đang được gọi.

Dưới đây là những gì tôi đang cố thử.

var myObjects = DbContext.Database 
    .SqlQuery<MyObject>("exec [dbo].[my_sproc] {0}", "some_value") 
    .ToList(); 

Tôi hiện chưa thử bất cứ điều gì vì không biết cách bắt đầu chế nhạo ví dụ này.

Các chế nhạo của DbSet dưới và tái lặp, tôi một cách chính xác có thể thử trả lại một DbSet của MyObject 's nhưng bây giờ đang cố gắng thử một sqlquery mà trả về một danh sách các MyObject' s.

var dbContext = new Mock<MyDbContext>(); 
dbContext.Setup(m => m.MyObjects).Returns(mockObjects.Object); 

dbContext.Setup(m => m.Database.SqlQuery... something along these lines 

Trả lời

14

Database.SqlQuery<T> không được đánh dấu là ảo, nhưng Set<T>.SqlQuery được đánh dấu là ảo.

Dựa trên Database.SqlQuery<T> tài liệu

Kết quả của truy vấn này không bao giờ được theo dõi bởi bối cảnh ngay cả khi loại của đối tượng quay trở lại là một loại thực thể. Sử dụng phương thức 'SqlQuery(String, Object[])' để trả về các đối tượng được theo dõi bởi ngữ cảnh .

Set<T>.SqlQuery tài liệu

Theo mặc định, các đối tượng quay trở lại được theo dõi bởi bối cảnh; điều này có thể được thay đổi bằng cách gọi AsNoTracking trên DbRawSqlQuery trả về.

thì Database.SqlQuery<T>(String, Object[]) nên tương đương với Set<T>.SqlQuery(String, Object[]).AsNoTracking() (chỉ khi T là thực thể EF, không phải là một DTO/VM).

Vì vậy, nếu bạn có thể thay thế việc thực hiện thành:

var myObjects = DbContext 
    .Set<MyObject>() 
    .SqlQuery("exec [dbo].[my_sproc] {0}", "some_value") 
    .AsNoTracking() 
    .ToList(); 

bạn có thể thử nó như sau

var list = new[] 
{ 
    new MyObject { Property = "some_value" }, 
    new MyObject { Property = "some_value" }, 
    new MyObject { Property = "another_value" } 
}; 

var setMock = new Mock<DbSet<MyObject>>(); 
setMock.Setup(m => m.SqlQuery(It.IsAny<string>(), It.IsAny<object[]>())) 
    .Returns<string, object[]>((sql, param) => 
    { 
     // Filters by property. 
     var filteredList = param.Length == 1 
      ? list.Where(x => x.Property == param[0] as string) 
      : list; 
     var sqlQueryMock = new Mock<DbSqlQuery<MyObject>>(); 
     sqlQueryMock.Setup(m => m.AsNoTracking()) 
      .Returns(sqlQueryMock.Object); 
     sqlQueryMock.Setup(m => m.GetEnumerator()) 
      .Returns(filteredList.GetEnumerator()); 
     return sqlQueryMock.Object; 
    }); 

var contextMock = new Mock<MyDbContext>(); 
contextMock.Setup(m => m.Set<MyObject>()).Returns(setMock.Object); 
+0

này làm việc rất lớn đối với tôi. Đối với tôi, điều này là thích hợp hơn để trừu tượng hóa logic truy vấn thành người trợ giúp, như trong câu trả lời được chấp nhận ở trên. – JamesWampler

8

Các Database tài sản và SqlQuery phương pháp không được đánh dấu là virtual để họ can't be mocked (sử dụng Moq, bạn có thể sử dụng một different library có thể giải thích cho điều này, nhưng có thể có nhiều quán tính hơn bạn muốn).

Bạn sẽ cần phải sử dụng một số loại trừu tượng để làm được việc này, chẳng hạn như bằng cách gói toàn bộ truy vấn cơ sở dữ liệu trong một lớp helper:

public interface IQueryHelper 
{ 
    IList<MyObject> DoYourQuery(string value); 
} 

public class QueryHelper : IQueryHelper 
{ 
    readonly MyDbContext myDbContext; 

    public QueryHelper(MyDbContext myDbContext) 
    { 
     this.myDbContext = myDbContext; 
    } 

    public IList<MyObject> DoYourQuery(string value) 
    { 
     return myDbContext.Database.SqlQuery<MyObject>("exec [dbo].[my_sproc] {0}", value).ToList(); 
    } 
} 

Bây giờ phương pháp bạn đang thử nghiệm trở thành:

public void YourMethod() 
{ 
    var myObjects = queryHelper.DoYourQuery("some_value"); 
} 

Sau đó, bạn hãy tiêm IQueryHelper vào hàm tạo của lớp bạn đang thử nghiệm và giả lập.

Bạn sẽ bị mất vùng phủ sóng thử nghiệm trên DoYourQuery, nhưng hiện tại truy vấn là so simple there are obviously no deficiencies.

5

Bạn có thể thêm một phương pháp ảo để bối cảnh cơ sở dữ liệu của bạn mà bạn có thể ghi đè trong các thử nghiệm đơn vị :

public partial class MyDatabaseContext : DbContext 
{ 
    /// <summary> 
    /// Allows you to override queries that use the Database property 
    /// </summary> 
    public virtual List<T> SqlQueryVirtual<T>(string query) 
    { 
     return this.Database.SqlQuery<T>(query).ToList(); 
    } 
} 
1

Nếu có ai gặp phải điều này. Tôi đã giải quyết vấn đề này với một vài cách tiếp cận. Chỉ là một cách khác để giải quyết vấn đề này.

  1. Ngữ cảnh của tôi bị tóm tắt thông qua giao diện. Tôi chỉ cần một vài trong số các phương pháp:

    public interface IDatabaseContext 
    { 
        DbSet<T> Set<T>() where T : class; 
        DbEntityEntry<T> Entry<T>(T entity) where T : class; 
        int SaveChanges(); 
        Task<int> SaveChangesAsync(); 
        void AddOrUpdateEntity<TEntity>(params TEntity[] entities) where TEntity : class; 
    

    }

  2. Tất cả các truy cập cơ sở dữ liệu của tôi là thông qua phương pháp async. Mà sẽ trả về một bộ hoàn toàn mới của các vấn đề khi cố gắng để thử nó. May mắn thay - nó đã được trả lời here. Ngoại lệ bạn nhận được liên quan đến mô hình còn thiếu cho IDbAsyncEnumerable. Sử dụng giải pháp được cung cấp - Tôi chỉ mở rộng thêm một chút để tôi có một trình trợ giúp trả về một đối tượng Mock> đã mô phỏng tất cả các thuộc tính được mong đợi.

    public static Mock<DbSqlQuery<TEntity>> CreateDbSqlQuery<TEntity>(IList<TEntity> data) 
        where TEntity : class, new() 
    { 
        var source = data.AsQueryable(); 
        var mock = new Mock<DbSqlQuery<TEntity>>() {CallBase = true}; 
        mock.As<IQueryable<TEntity>>().Setup(m => m.Expression).Returns(source.Expression); 
        mock.As<IQueryable<TEntity>>().Setup(m => m.ElementType).Returns(source.ElementType); 
        mock.As<IQueryable<TEntity>>().Setup(m => m.GetEnumerator()).Returns(source.GetEnumerator()); 
        mock.As<IQueryable<TEntity>>().Setup(m => m.Provider).Returns(new TestDbAsyncQueryProvider<TEntity>(source.Provider)); 
        mock.As<IDbAsyncEnumerable<TEntity>>().Setup(m => m.GetAsyncEnumerator()).Returns(new TestDbAsyncEnumerator<TEntity>(data.GetEnumerator())); 
        mock.As<IDbSet<TEntity>>().Setup(m => m.Create()).Returns(new TEntity()); 
        mock.As<IDbSet<TEntity>>().Setup(m => m.Add(It.IsAny<TEntity>())).Returns<TEntity>(i => { data.Add(i); return i; }); 
        mock.As<IDbSet<TEntity>>().Setup(m => m.Remove(It.IsAny<TEntity>())).Returns<TEntity>(i => { data.Remove(i); return i; }); 
        return mock; 
    } 
    
  3. Cuối cùng - bằng cách sử dụng giải pháp được cung cấp bởi @Yulium Chandra - thử nghiệm của tôi về SQL liệu với bối cảnh chế giễu trông giống như:

    public Mock<DbSet<TestModel>> MockDbSet { get; } 
    
        .... 
    
        MockDbSet.Setup(x => x.SqlQuery(It.IsAny<string>)) 
          .Returns<string,object[]> 
          ((sql, param) => 
          { 
           var sqlQueryMock = MockHelper.CreateDbSqlQuery(Models); 
    
           sqlQueryMock.Setup(x => x.AsNoTracking()) 
            .Returns(sqlQueryMock.Object); 
    
           return sqlQueryMock.Object; 
          }); 
    
Các vấn đề liên quan