2016-05-17 6 views
5

エンティティフレームワーク(コードファーストアプローチ)を使用したN層アプリケーションがあります。今私はいくつかのテストを自動化したいと思います。私はMoqフレームワークを使用しています。私はテストを書くことについていくつかの問題を発見しています。おそらく私の建築は間違っているでしょうか?間違っていると、私はよく分離されていないコンポーネントを書いているので、テスト可能ではありません。私は本当にこれが好きではない...またはおそらく、私は単にmoqフレームワークを正しく使用することはできません。エンティティフレームワークをN層アーキテクチャでモックする方法

私はあなたが私のアーキテクチャを見てみましょう:私はクラスのコンストラクタで、私のcontextを注入あらゆるレベルで

enter image description here

を。

外観:

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

    public PublicAreaFacade(IDataContext context) 
    { 
     _unitOfWork = new UnitOfWork(context); 
    } 
} 

BLL:

public abstract class BaseManager 
{ 
    protected IDataContext Context; 

    public BaseManager(IDataContext context) 
    { 
     this.Context = context; 
    } 
} 

レポジトリ:

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

    public Repository(IDataContext context) 
    { 
     this._context = context as PublicAreaContext; 
    } 
} 

IDataContext私DbContextによって実装されるインターフェースである。

public partial class PublicAreaContext : DbContext, IDataContext 

さて、私はEFを模擬し、どのように私はテストを書く方法:

[TestInitialize] 
public void Init() 
{ 
    this._mockContext = ContextHelper.CreateCompleteContext(); 
} 

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

そして、ここで私は私のテストを書く方法:

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

ここでは、それはすべて正常に動作するようです!そして私の建築が正しいように見えます。しかし、エンティティを挿入/更新したいのですが?もう何も働かない!私は理由を説明:

あなたが見ることができるように、私はその後、私のTOAに、私はDTOのpropertiessから私のエンティティを生成、ファサードに(それはDTOです)*Requestオブジェクトを渡す:

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

    return trace; 
} 

PaymentAttemptTrace Entity Frameworkに挿入するエンティティです。これは嘲笑されず、挿入できません。したがって、私の虚偽のコンテキスト(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 
} 

ファサード:私はリポジトリを書いたか最後に

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

::私はPaymentsManagerを開発した方法をここで

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

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); 
    } 
} 
+1

エンティティの挿入/更新に関するテストを表示して、どのように正確に失敗するか説明してください。また、テスト対象のコードも参考になります。 –

+0

私の質問を例文 – Ciccio

答えて

2

あなたのアーキテクチャはうまく見えますが、実装に欠陥があります。漏れ抽象です。です。

ダイアグラムでファサード層のみBLLに依存しますが、あなたはPublicAreaFacadeのコンストラクタを見たとき、あなたは実際にそれがリポジトリ層からインターフェイスに直接依存していることがわかります。

public PublicAreaFacade(IDataContext context) 
{ 
    _unitOfWork = new UnitOfWork(context); 
} 

これは必須ではありません。それが唯一の入力としてその直接の依存関係を取る必要があります - PaymentsManagerか - をさらに良い - それのインタフェース:

public PublicAreaFacade(IPaymentsManager paymentsManager) 
{ 
    ... 
} 

をconcequenceは、あなたのコードは方法よりテスト可能になることです。あなたのテストを見てみると、あなたのシステムの最も外側の層の1つ(PublicAreaFacade)のをテストしているのに、あなたのシステムの最も内側の層(つまり、IDataContext、さらにはエンティティアクセサSet<TEntity>)クラス)。

これはPublicAreaFacadeだけIPaymentsManagerに依存している場合TracePaymentAttemptメソッドのユニットテストは次のようになります方法です:

[TestMethod] 
public void CallsPaymentManagerWithRequestDataWhenTracingPaymentAttempts() 
{ 
    // Arrange 
    var pm = new Mock<IPaymentsManager>(); 
    var pa = new PulicAreaFacade(pm.Object); 
    var payment = new TracePaymentAttemptRequest 
     { 
      ... 
     } 

    // Act 
    pa.TracePaymentAttempt(payment); 

    // Assert that we call the correct method of the PaymentsManager with the data from 
    // the request. 
    pm.Verify(pm => pm.SavePaymentAttemptResult(
     It.IsAny<string>(), 
     payment.CodiceTransazione, 
     payment.EsitoPagamento + " - " + payment.DescrizioneEsitoPagamento, 
     payment.Email, 
     payment.AliasTerminale, 
     payment.NumeroContratto, 
     It.IsAny<int>(), 
     payment.TotalePagamento, 
     It.IsAny<DateTime>())) 
} 
+0

'unitOfWork'には、怠惰な方法でインスタンス化されている必要なマネージャがすべて含まれています。' IDataContext'について私はあなたに同意します。私はそれに合格しません。私は2つの問題があります。 1.コンテキストがBLLの背後に隠れている場合、どのようにコンテキストをモックできますか?あなたが私のテストを見たら、私は文脈を嘲笑してから、それをファサードに入力して渡します。 2. 1 Facadeは異なるマネージャーを呼び出すことができます。マネージャはコンテキストを使用します。私が文脈に入力した文脈を渡さないと、どのようにして各マネージャーに対してただ一つの文脈を作り出すことができますか? – Ciccio

+1

1)トリックは、ファサードをテストするときにコンテキストをモックする必要がないということです。ファサードの方法をテストする場合は、ファサード自体が何をしているのかをテストしたいだけで、暗黙のうちに何が起こっているのかはテストしたくないのです。 –

+1

2)私があなたにしたいことは、依存性注入*と呼ばれています。 DIでは、通常、アプリケーションのエントリポイントに完全なオブジェクトグラフを作成します。あなたの場合、それはRESTサービスエンドポイントです。しかし、MicorsoftのUnityのようなDIフレームワークを使用して、オブジェクトグラフの作成をずっと簡単にすることもできます。 –

0

IUnitOfWorkをファサードまたはBLLレイヤーのコンストラクターに渡します。次に、あなたのテストでMock<IUnitOfWork>が返すものをセットアップできます。多分repoコンストラクタと作業ユニットを除いて、すべてにIDataContextを渡す必要はありません。

ファサードUnitOfWorkコールを通じてレポ呼び出しを行う方法PrepareEasyPayPayment、セットアップこのようなモック持っている場合たとえば、:

// Arrange 
var unitOfWork = new Mock<IUnitOfWork>(); 
unitOfWork.Setup(x => x.PrepareEasyPayPaymentRepoCall(request)).Returns(true); 
var paymentFacade = new PaymentFacade(unitOfWork.Object); 

// Act 
var result = paymentFacade.PrepareEasyPayPayment(request); 

を次にあなたがより簡単にデータ呼を嘲笑することができましたFacadeでコードをテストしてください。

インサートテストの場合、CreatePaymentのようなFacadeメソッドがあり、これにはPrepareEasyPayPaymentRequestが必要です。そのCreatePaymentメソッド内で、それはあなたがユニットテストのためのモックとしたい何

var result = _unitOfWork.CreatePaymentRepoCall(request); 
if (result == true) 
{ 
    // yes! 
} 
else 
{ 
    // oh no! 
} 

のように、おそらく仕事のユニットを介して、レポを参照する必要があり、この作成/挿入レポコールがtrueまたはfalseを返しますので、あなたがテストすることができるということですレポコールが完了した後にコードが分岐します。

また、挿入呼び出しが期待どおりに行われたかどうかをテストすることもできますが、その呼び出しのパラメータには多くのロジックが組み込まれていない限り、通常は価値がありません。

+0

で更新しました。理解していますが、あなたの解決策は私の問題を解決しません。私はすでに 'PrepareEasyPayPaymentRequest'をタップする' CreatePayment'のようなメソッドを持っています。 Facadeのコンストラクタに 'IUnitOfWork'を渡すために私のファサードを変更することができます。 しかし、問題は残っています!問題は、ファサード内でデータベース内に挿入するエンティティ(Entity Framework)を作成することです。私はエンティティをインスタンス化します...私は '新しい'を行います...このようにしてエンティティは嘲笑されませんので、挿入物をテストできません。 probを解決する正しい方法ではない – Ciccio

0

コードを少し変更する必要があるようです。新しいものは、ハードコードされた依存関係を導入し、それらをテスト不能にするので、それらを抽象化してみてください。たぶん、別のレイヤーの背後にあるEFをすべて非表示にすることができます。その場合、特定のレイヤーレイヤーをモックしてEFに触れることは一切ありません。

0

あなたはモックエンティティフレームワークdbcontextに良いユニットテストのために、このオープンソースのフレームワークを使用することができます

https://effort.codeplex.com/

これは効率的にデータを模擬するのに役立ちます試してみてください。

関連する問題