如何在N层架构中模拟实体框架

Cic*_*cio 5 unit-testing entity-framework moq mocking n-layer

我有一个带有实体框架的N层应用程序(代码优先方法).现在我想自动化一些测试.我正在使用Moq框架.我发现编写测试有些问题.也许我的架构错了?如果错误,我的意思是我编写的组件不是很好,因此它们不可测试.我真的不喜欢这个......或许,我根本无法正确使用moq框架.

我让你看看我的架构:

在此输入图像描述

在每个级别,我都会context在类的构造函数中注入.

门面:

public class PublicAreaFacade : IPublicAreaFacade, IDisposable
{
    private UnitOfWork _unitOfWork;

    public PublicAreaFacade(IDataContext context)
    {
        _unitOfWork = new UnitOfWork(context);
    }
}
Run Code Online (Sandbox Code Playgroud)

BLL:

public abstract class BaseManager
{
    protected IDataContext Context;

    public BaseManager(IDataContext context)
    {
        this.Context = context;
    }
}
Run Code Online (Sandbox Code Playgroud)

存储库:

public class Repository<TEntity>
    where TEntity : class
{
    internal PublicAreaContext _context;
    internal DbSet<TEntity> _dbSet;

    public Repository(IDataContext context)
    {
        this._context = context as PublicAreaContext;
    }
}
Run Code Online (Sandbox Code Playgroud)

IDataContext 是一个由我的DbContext实现的接口:

public partial class PublicAreaContext : DbContext, IDataContext
Run Code Online (Sandbox Code Playgroud)

现在,我如何模拟EF以及如何编写测试:

[TestInitialize]
public void Init()
{
    this._mockContext = ContextHelper.CreateCompleteContext();
}
Run Code Online (Sandbox Code Playgroud)

在哪里ContextHelper.CreateCompleteContext():

public static PublicAreaContext CreateCompleteContext()
{
    //Here I mock my context
    var mockContext = new Mock<PublicAreaContext>();

    //Here I mock my entities
    List<Customer> customers = new List<Customer>()
    {
        new Customer() { Code = "123455" }, //Customer with no invoice
        new Customer() { Code = "123456" }
    };

    var mockSetCustomer = ContextHelper.SetList(customers);
    mockContext.Setup(m => m.Set<Customer>()).Returns(mockSetCustomer);

    ...

    return mockContext.Object;
}
Run Code Online (Sandbox Code Playgroud)

在这里我如何写我的测试:

[TestMethod]
public void Success()
{
    #region Arrange
    PrepareEasyPayPaymentRequest request = new PrepareEasyPayPaymentRequest();
    request.CodiceEasyPay = "128855248542874445877";
    request.Servizio = "MyService";
    #endregion

    #region Act
    PublicAreaFacade facade = new PublicAreaFacade(this._mockContext);
    PrepareEasyPayPaymentResponse response = facade.PrepareEasyPayPayment(request);
    #endregion

    #region Assert
    Assert.IsTrue(response.Result == it.MC.WebApi.Models.ResponseDTO.ResponseResult.Success);
    #endregion
}
Run Code Online (Sandbox Code Playgroud)

这似乎它正常工作!看起来我的架构是正确的.但是,如果我想插入/更新实体怎么办?什么都没有了!我解释原因:

如你所见,我将一个*Request对象(它是DTO)传递给了外观,然后在我的TOA中,我从DTO的属性中生成了我的实体:

private PaymentAttemptTrace CreatePaymentAttemptTraceEntity(string customerCode, int idInvoice, DateTime paymentDate)
{
    PaymentAttemptTrace trace = new PaymentAttemptTrace();
    trace.customerCode = customerCode;
    trace.InvoiceId = idInvoice;
    trace.PaymentDate = paymentDate;

    return trace;
}
Run Code Online (Sandbox Code Playgroud)

PaymentAttemptTrace是我将插入实体框架的实体..它没有被嘲笑,我不能注入它.所以,即使我通过我的模拟上下文(IDataContext),当我尝试插入一个未被模拟的实体时,我的测试失败了!

这里怀疑我有一个错误的架构已经提出!

那么,怎么了?我使用moq的架构或方式?

谢谢你的帮助

UPDATE

我在这里测试我的代码..例如,我想测试付款的跟踪..

在这里测试:

[TestMethod]
public void NoPaymentDate()
{
    TracePaymentAttemptRequest request = new TracePaymentAttemptRequest();
    request.AliasTerminale = "MyTerminal";
    //...
    //I create my request object

    //You can see how I create _mockContext above
    PublicAreaFacade facade = new PublicAreaFacade(this._mockContext);
    TracePaymentAttemptResponse response = facade.TracePaymentAttempt(request);

    //My asserts
}
Run Code Online (Sandbox Code Playgroud)

这里的立面:

public TracePaymentAttemptResponse TracePaymentAttempt(TracePaymentAttemptRequest request)
{
    TracePaymentAttemptResponse response = new TracePaymentAttemptResponse();

    try
    {
        ...

        _unitOfWork.PaymentsManager.SavePaymentAttemptResult(
            easyPay.CustomerCode, 
            request.CodiceTransazione,
            request.EsitoPagamento + " - " + request.DescrizioneEsitoPagamento, 
            request.Email, 
            request.AliasTerminale, 
            request.NumeroContratto, 
            easyPay.IdInvoice, 
            request.TotalePagamento,
            paymentDate);

        _unitOfWork.Commit();

        response.Result = ResponseResult.Success;
    }
    catch (Exception ex)
    {
        response.Result = ResponseResult.Fail;
        response.ResultMessage = ex.Message;
    }

    return response;
}
Run Code Online (Sandbox Code Playgroud)

我在这里如何开发PaymentsManager:

public PaymentAttemptTrace SavePaymentAttemptResult(string customerCode, string transactionCode, ...)
{
    //here the problem... PaymentAttemptTrace is the entity of entity framework.. Here i do the NEW of the object.. It should be injected, but I think it would be a wrong solution
    PaymentAttemptTrace trace = new PaymentAttemptTrace();
    trace.customerCode = customerCode;
    trace.InvoiceId = idInvoice;
    trace.PaymentDate = paymentDate;
    trace.Result = result;
    trace.Email = email;
    trace.Terminal = terminal;
    trace.EasypayCode = transactionCode;
    trace.Amount = amount;
    trace.creditCardId = idCreditCard;
    trace.PaymentMethod = paymentMethod;

    Repository<PaymentAttemptTrace> repository = new Repository<PaymentAttemptTrace>(base.Context);
    repository.Insert(trace);

    return trace;
}
Run Code Online (Sandbox Code Playgroud)

最后我是如何编写存储库的:

public class Repository<TEntity>
    where TEntity : class
{
    internal PublicAreaContext _context;
    internal DbSet<TEntity> _dbSet;

    public Repository(IDataContext context)
    {  
        //the context is mocked.. Its type is {Castle.Proxies.PublicAreaContextProxy}
        this._context = context as PublicAreaContext;
        //the entity is not mocked. Its type is {PaymentAttemptTrace} but should be {Castle.Proxies.PaymentAttemptTraceProxy}... so _dbSet result NULL
        this._dbSet = this._context.Set<TEntity>();
    }

    public virtual void Insert(TEntity entity)
    {
        //_dbSet is NULL so "Object reference not set to an instance of an object" exception is raised
        this._dbSet.Add(entity);
    }
}
Run Code Online (Sandbox Code Playgroud)

Goo*_*ide 2

您的架构看起来不错,但实现存在缺陷。它正在泄漏抽象

\n\n

在你的图中,Fa\xc3\xa7ade层仅依赖于BLL,但是当你查看\ 的构造函数时,你会发现实际上它直接依赖于存储库PublicAreaFacade的接口:

\n\n
public PublicAreaFacade(IDataContext context)\n{\n    _unitOfWork = new UnitOfWork(context);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这不应该是这样。它应该只将其直接依赖项作为输入 -PaymentsManager或者 - 甚至更好 - 它的接口:

\n\n
public PublicAreaFacade(IPaymentsManager paymentsManager)\n{\n    ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

结果是您的代码变得更加可测试。当您现在查看测试时,您会发现您必须模拟系统的最内层(即IDataContext甚至它的实体访问器Set<TEntity>),尽管您正在测试系统的最外层之一PublicAreaFacade(类)。

\n\n

TracePaymentAttempt如果PublicAreaFacade仅依赖于以下内容,则该方法的单元测试将如下所示IPaymentsManager

\n\n
[TestMethod]\npublic void CallsPaymentManagerWithRequestDataWhenTracingPaymentAttempts()\n{\n    // Arrange\n    var pm = new Mock<IPaymentsManager>();\n    var pa = new PulicAreaFacade(pm.Object);\n    var payment = new TracePaymentAttemptRequest\n        {\n            ...\n        }\n\n    // Act\n    pa.TracePaymentAttempt(payment);\n\n    // Assert that we call the correct method of the PaymentsManager with the data from\n    // the request.\n    pm.Verify(pm => pm.SavePaymentAttemptResult(\n        It.IsAny<string>(), \n        payment.CodiceTransazione,\n        payment.EsitoPagamento + " - " + payment.DescrizioneEsitoPagamento,\n        payment.Email,\n        payment.AliasTerminale,\n        payment.NumeroContratto,\n        It.IsAny<int>(),\n        payment.TotalePagamento,\n        It.IsAny<DateTime>()))\n}\n
Run Code Online (Sandbox Code Playgroud)\n