2

ASP .NET MVC コントローラー クラスの単体テストをいくつか作成していますが、非常に奇妙なエラーが発生しました。

私のコントローラーコードは以下の通りです:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(JournalViewModel journal)
{
    var selectedJournal = Mapper.Map<JournalViewModel, Journal>(journal);

    var opStatus = _journalRepository.DeleteJournal(selectedJournal);
    if (!opStatus.Status)
        throw new System.Web.Http.HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));

    return RedirectToAction("Index");
}

私のテストコードは以下の通りです:

[TestMethod]
public void Delete_Journal()
{
    // Arrange

    // Simulate PDF file
    HttpPostedFileBase mockFile = Mock.Create<HttpPostedFileBase>();
    Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf");
    Mock.Arrange(() => mockFile.ContentLength).Returns(255);

    // Create view model to send.
    JournalViewModel journalViewModel = new JournalViewModel();
    journalViewModel.Id = 1;
    journalViewModel.Title = "Test";
    journalViewModel.Description = "TestDesc";
    journalViewModel.FileName = "TestFilename.pdf";
    journalViewModel.UserId = 1;
    journalViewModel.File = mockFile; // Add simulated file

    Mock.Arrange(() => journalRepository.DeleteJournal(null)).Returns(new OperationStatus
    {
        Status = true
    });

    // Act
    PublisherController controller = new PublisherController(journalRepository, membershipRepository);
    RedirectToRouteResult result = controller.Delete(journalViewModel) as RedirectToRouteResult;

    // Assert
    Assert.AreEqual(result.RouteValues["Action"], "Index");
}

問題 1 - マッピングの例外:

テストを実行するたびに、次の例外が発生します。

テスト名: Delete_Journal テストのフルネーム
: Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal
テスト ソース: \Source\Journals.Web.Tests\Controllers\PublisherControllerTest.cs: 132 行
目 テスト結果: 失敗 テスト期間: 0:00:00 ,3822468

結果 StackTrace: \Source\Journals.Web\Controllers\PublisherController.cs の Journals.Web.Controllers.PublisherController.Delete(JournalViewModel ジャーナル):\Source の Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal() の 81 行目\Journals.Web.Tests\Controllers\PublisherControllerTest.cs:156 行目結果メッセージ: テスト メソッド Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal が例外をスローしました: AutoMapper.AutoMapperMappingException: 型マップ構成が見つからないか、マッピングがサポートされていません。

マッピング タイプ: JournalViewModel -> Journal Journals.Model.JournalViewModel -> Journals.Model.Journal

宛先パス: ジャーナル

ソース値: Journals.Model.JournalViewModel

JournalViewModelクラスとの間にマッピングの問題があるようですが、Journalそれがどこにあるのかわかりません。このコードをApplication_Startinに追加しましたGlobal.asax.cs:

Mapper.CreateMap<Journal, JournalViewModel>();
Mapper.CreateMap<JournalViewModel, Journal>();

JournalからへのマッピングJournalViewModelが機能しています。

Mapper.CreateMap<JournalViewModel, Journal>();最後に、メソッドの最初の行として追加しようとしたところDelete、すべてが機能しましたが、理由はわかりません。

問題 2 - HTML 例外

上記の回避策でマッピングが実行されると、Mock を使用してオーバーライドして常に true にしたにもかかわらず、プロパティStatusfromが常に false になるという問題が発生します。var opStatus = _journalRepository.DeleteJournal(selectedJournal);これにより、発生してはならない HTML 例外がスローされます。

編集

Application_Start を次のように変更しました。

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<Journal, JournalViewModel>();
    cfg.CreateMap<JournalViewModel, Journal>();
});

しかし、私はまだ同じエラーがあります。

編集 - 問題2の解決

ユニットテストクラスにマッピングを追加するのを忘れていたことが判明したため、次のことを行いました。

[TestInitialize]
public void TestSetup()
{
    // Create necessary mappings
    Mapper.CreateMap<Journal, JournalViewModel>();
    Mapper.CreateMap<JournalViewModel, Journal>();

    //...other code omitted for brevity
}

そして、これが問題の原因であることが判明しました。Global.asax.cs Application_Start() は単体テストで呼び出されないため、マッピングが作成されないため、単体テストの初期化で自分でこれを行う必要があったと思います。

4

1 に答える 1

5

問題1

Automapper には、静的 API とインスタンス APIの両方があります。インスタンス API の使用を検討し、IMapperそれをコントローラーに挿入する必要があります。

public class PublisherController : Controller {
    private readonly IMapper mapper;

    public PublisherController(IJournalRepository journalRepository, IMembershipRepositry membershipRepository, IMapper mapper) {
        //...other code omitted for brevity
        this.mapper = mapper;
    }

    //...other code omitted for brevity

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Delete(JournalViewModel journal) {
        var selectedJournal = mapper.Map<JournalViewModel, Journal>(journal);

        var opStatus = _journalRepository.DeleteJournal(selectedJournal);
        if (!opStatus.Status)
            throw new System.Web.Http.HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));

        return RedirectToAction("Index");
    }
}

これにより、必要に応じてマッピングのより良いモック/偽造/構成が可能になります。IMapperコントローラーへの依存性注入を確実に構成する必要があります。

インスタンス API に変更できない場合は、テストを実行する前にマッパーが初期化されていることを確認する必要があります。

Mapper.Initialize(cfg => {
    cgf.CreateMap<JournalViewModel, Journal>();
});

問題 2

テストでのあなたの配置は

Mock.Arrange(() => journalRepository.DeleteJournal(null)).Returns(new OperationStatus
{
    Status = true
});

journalRepository.DeleteJournalあなたが気づいたように、これは実際のインスタンスで呼び出す場合には機能しません。Telerik の JustMock を使用していると仮定すると、より柔軟な議論を用意する必要があります。

Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny<Journal>())).Returns(new OperationStatus
{
    Status = true
});

出典 : JustMock アレンジメントでの引数の処理

完全なテスト: インスタンス API

[TestMethod]
public void Delete_Journal() {
    // Arrange

    //Configure mapping just for this test but something like this
    //should be in accessible from your composition root and called here.
    var config = new MapperConfiguration(cfg => {
        cfg.CreateMap<Journal, JournalViewModel>();
        cfg.CreateMap<JournalViewModel, Journal>();
    });

    var mapper = config.CreateMapper(); // IMapper

    // Simulate PDF file
    var mockFile = Mock.Create<HttpPostedFileBase>();
    Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf");
    Mock.Arrange(() => mockFile.ContentLength).Returns(255);

    // Create view model to send.
    var journalViewModel = new JournalViewModel();
    journalViewModel.Id = 1;
    journalViewModel.Title = "Test";
    journalViewModel.Description = "TestDesc";
    journalViewModel.FileName = "TestFilename.pdf";
    journalViewModel.UserId = 1;
    journalViewModel.File = mockFile; // Add simulated file

    var status = new OperationStatus {
        Status = true
    };

    Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny<Journal>())).Returns(status);

    var controller = new PublisherController(journalRepository, membershipRepository, mapper);

    // Act        
    var result = controller.Delete(journalViewModel) as RedirectToRouteResult;

    // Assert
    Assert.AreEqual(result.RouteValues["Action"], "Index");
}

完全なテスト: 静的 API

[TestMethod]
public void Delete_Journal() {
    // Arrange

    //Configure mapping just for this test but something like this
    //should be in accessible from your composition root and called here.
    Mapper.Initialize(cfg => {
        cfg.CreateMap<Journal, JournalViewModel>();
        cfg.CreateMap<JournalViewModel, Journal>();
    });

    // Simulate PDF file
    var mockFile = Mock.Create<HttpPostedFileBase>();
    Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf");
    Mock.Arrange(() => mockFile.ContentLength).Returns(255);

    // Create view model to send.
    var journalViewModel = new JournalViewModel();
    journalViewModel.Id = 1;
    journalViewModel.Title = "Test";
    journalViewModel.Description = "TestDesc";
    journalViewModel.FileName = "TestFilename.pdf";
    journalViewModel.UserId = 1;
    journalViewModel.File = mockFile; // Add simulated file

    var status = new OperationStatus {
        Status = true
    };

    Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny<Journal>())).Returns(status);

    var controller = new PublisherController(journalRepository, membershipRepository);

    // Act        
    var result = controller.Delete(journalViewModel) as RedirectToRouteResult;

    // Assert
    Assert.AreEqual(result.RouteValues["Action"], "Index");
}
于 2016-10-05T07:48:18.067 に答える