6

私は、最新のASP.NET MVCプロジェクトを構築している間に、Unit Testing、Dependancy Injection、そしてそのすべてのジャズに入るようになっています。IoCコンテナなしでコントローラをどのようにユニットテストできますか?

ここで、私はユニットテストをしたいと思っていますが、IoCコンテナなしでこれを適切に行う方法がわかりません。

例えば、単純なコントローラーください:このクラスは、理由SqlQuestionsRepositoryのその直接のインスタンス化の非常にユニットテスト可能ではありません

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository _repository = new SqlQuestionsRepository(); 

    // ... Continue with various controller actions 
} 

を。だから、依存性の注入ルートを下ると行うことができます:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository _repository; 

    public QuestionsController(IQuestionsRepository repository) 
    { 
     _repository = repository; 
    } 
} 

これは良さそうです。 IQuestionsRepositoryで単体テストを簡単に書くことができます。しかし、今コントローラをインスタンス化しようとしていますか?コールチェーンSqlQuestionRepositoryをインスタンス化する必要があります。私は単に問題を他の場所に移しただけで、それを取り除かなかったようです。

これは、IoCコンテナが私のためにコントローラの依存関係を配線すると同時に、コントローラを簡単にユニットテスト可能に保つことによって助けになる良い例であることが分かりました。

私の質問は、この性質のものについてユニットテストをどうやって行うのですか IoCコンテナなし?

注:私はIoCコンテナに反対していません。すぐにその道を行く可能性があります。しかし、私はそれらを使用しない人々のための代替は何かが不思議です。

答えて

2

フィールドの直接的なインスタンス化を維持したり、セッターを提供することはできませんか?この場合、単体テスト中にセッターを呼び出すだけです。このような何か:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository _repository = new SqlQuestionsRepository(); 

    // Really only called during unit testing... 
    public QuestionsController(IQuestionsRepository repository) 
    { 
     _repository = repository; 
    } 
} 

私は、.NETとあまり慣れていないんだが、Javaでのサイドノートとして、これはテスト容易性を向上させるために、既存のコードをリファクタリングする一般的な方法です。既に使用されているクラスがあり、既存の機能を損なうことなくコードカバレッジを改善するためにクラスを変更する必要がある場合は、I.E.

私たちのチームはこれまでにこの作業を行っていましたが、通常はsetterの可視性をpackage-privateに設定し、テストクラスのパッケージを同じにしてsetterを呼び出すことができます。

+0

@Peter、非常に良い提案。あなたのプロジェクトでIoCコンテナを使用した経験がありますか、それともまだ必要がないのですか? – mmcdole

+0

私は.NETの経験はあまりなく、私の仕事の大半はIoCのSpringを使ったJavaのものです。これは、モジュール性を改善するのに非常に役立ちました。 – Peter

+0

@Peter:これは私が使用しているパターンとほぼ同じです。私は同じことをする答えを掲示しましたが、あなたがJavaの人であることを意識していないかもしれないC#の言語機能を使用しています。 –

2

デフォルトのコンストラクタをコントローラで使用すると、何らかのデフォルトの動作が発生する可能性があります。

のような何か...コントローラの工場が、それはデフォルトコンストラクタの動作を使用しますコントローラの新しいインスタンスを作成され、デフォルトで

public QuestionsController() 
    : this(new QuestionsRepository()) 
{ 
} 

その方法。ユニットテストでは、モックフレームワークを使用して、他のコンストラクタにモックを渡すことができます。

1

1つのオプションは、偽物を使用することです。

public class FakeQuestionsRepository : IQuestionsRepository { 
    public FakeQuestionsRepository() { } //simple constructor 
    //implement the interface, without going to the database 
} 

[TestFixture] public class QuestionsControllerTest { 
    [Test] public void should_be_able_to_instantiate_the_controller() { 
     //setup the scenario 
     var repository = new FakeQuestionsRepository(); 
     var controller = new QuestionsController(repository); 
     //assert some things on the controller 
    } 
} 

もう1つの選択肢は、モックとモックフレームワークを使用して、これらのモックをオンザフライで自動的に生成することです。

[TestFixture] public class QuestionsControllerTest { 
    [Test] public void should_be_able_to_instantiate_the_controller() { 
     //setup the scenario 
     var repositoryMock = new Moq.Mock<IQuestionsRepository>(); 
     repositoryMock 
      .SetupGet(o => o.FirstQuestion) 
      .Returns(new Question { X = 10 }); 
     //repositoryMock.Object is of type IQuestionsRepository: 
     var controller = new QuestionsController(repositoryMock.Object); 
     //assert some things on the controller 
    } 
} 

すべてのオブジェクトがどこで構築されるかについて。単体テストでは、テスト対象の実際のオブジェクトと、テスト対象の実際のオブジェクトに必要な一部の擬似または擬似依存関係を作成します。たとえば、実際の被験体はQuestionsControllerのインスタンスです。IQuestionsRepositoryに依存しているので、第1の例のように偽のIQuestionsRepository、第2の例のように模擬IQuestionsRepositoryを与えます。

しかし、実際のシステムでは、コンテナ全体がソフトウェアの最上位レベルに設定されています。たとえば、Webアプリケーションでは、すべてのインターフェイスと実装クラスを結ぶコンテナをGlobalApplication.Application_Startに設定します。

0

私はピーターの答えで少し拡大しています。

多くのエンティティタイプのアプリケーションでは、コントローラが複数のリポジトリやサービスなどの参照を要求することは珍しいことではありません。私は、テストコード内のすべての依存関係を手作業で渡すのは面倒です(特に、テストには1つまたは2つしか関係しないため)。そのようなシナリオでは、私はセッター注入型のIOCをコンストラクター注入よりも優先します。私はこのそれを使用パターン:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository Repository 
    { 
     get { return _repo ?? (_repo = IoC.GetInstance<IQuestionsRepository>()); } 
     set { _repo = value; } 
    } 
    private IQuestionsRepository _repo; 

    // Don't need anything fancy in the ctor 
    public QuestionsController() 
    { 
    } 
} 

はあなたの特定のIOCフレームワークを使用しています何の構文でIoC.GetInstance<>を交換してください。

プロダクションでは、何もプロパティ設定ツールを呼び出さないので、ゲッターが初めて呼び出されたときにコントローラはIOCフレームワークを呼び出してインスタンスを取得し、格納します。

テストでは、直前に任意のコントローラメソッドを呼び出すにセッターを呼び出す必要があります。このアプローチの

var controller = new QuestionsController { 
    Repository = MakeANewMockHoweverYouNormallyDo(...); 
} 

メリット、私見:

  1. はまだ生産にIOCを利用しています。
  2. テスト中に手動でコントローラを構築する方が簡単です。テストで実際に使用する依存関係を初期化するだけで済みます。
  3. 一般的な依存関係を手動で構成したくない場合は、テスト固有のIOC構成を作成できます。
関連する問題