2017-09-14 15 views
1

저는 sitecore 개발자이며 "EmailArticleController"컨트롤러에서 볼 수있는 로직을 테스트하기위한 샘플 Sitecore 헬릭스 유닛 테스트 프로젝트를 만들고 싶습니다. 색인() 액션 메소드 : 그것은 (문자열) 데이터 소스를 가지고Sitecore.Mvc.Presentation.RenderingContext를 사용하는 GlassController 작업 단위 테스트 방법

[TestClass] 
public class UnitTest1 
{ 
    [TestMethod] 
    public void Test_EmailArticleController_With_RenderingContext() 
    { 
     //Arrange 
     var businessLogicFake = new Mock<IEmailArticleBusiness>(); 

     var model = new EmailArticleViewModel() 
     { 
      ArticleControl = new Article_Control() { }, 
      Metadata = new Metadata() { } 
     }; 

     businessLogicFake.Setup(x => x.FetchPopulatedModel).Returns(model); 

     // I'm not sure about the next 3 lines, how do I mock the RenderingContext and send it into the constructor, given that it needs a DataSource value too? 
     var renderingContext = Mock.Of<Sitecore.Mvc.Presentation.RenderingContext>(/*what goes here, if anything?*/) { /*what goes here, if anything?*/ }; 

     EmailArticleController controllerUnderTest = new EmailArticleController(businessLogicFake.Object, renderingContext); 

     var result = controllerUnderTest.Index(3) as ViewResult; 

     Assert.IsNotNull(result); 
    } 
} 

은 기본적으로 내가, 렌더링 컨텍스트를 조롱 있는지 확인하려면이

using Sitecore.Mvc.Presentation; 

public class EmailArticleController : GlassController 
{ 
    //logic in below Index() method is what I want to test 
    public override ActionResult Index() 
    { 
     var _emailArticleBusiness = new EmailArticleBusiness(); 
     var model = _emailArticleBusiness.FetchPopulatedModel; 
     var datasourceId = RenderingContext.Current.Rendering.DataSource; 
     _emailArticleBusiness.SetDataSourceID(datasourceId); 

     return View("~/Views/EmailCampaign/EmailArticle.cshtml", model); 
    } 

    //below is alternative code I wrote for mocking and unit testing the logic in above Index() function 
    private readonly IEmailArticleBusiness _businessLogic; 
    private readonly RenderingContext _renderingContext; 

    public EmailArticleController(IEmailArticleBusiness businessLogic, RenderingContext renderingContext) 
    { 
     _businessLogic = businessLogic; 
     _renderingContext = renderingContext; 
    } 

    public ActionResult Index(int forUnitTesting) 
    { 
     var model = _businessLogic.FetchPopulatedModel; 
     // *** do below two lines of logic somehow go into my Unit Testing class? How? 
     var datasourceId = _renderingContext.Rendering.DataSource; 
     _businessLogic.SetDataSourceID(datasourceId); 
     // *** 
     return View("~/Views/EmailCampaign/EmailArticle.cshtml", model); 
    } 
} 

확인을 내 단위 테스트 클래스에있는 것입니다 값을 "/ sitecore/home/..."과 같이 설정하면 I w 개미가 컨트롤러의 생성자 (적절한 방법이라면)로 보내고, Index (int) 메서드를 호출하고, 동시에이 경우 인터페이스 인 _businessLogic을 확인하십시오 (구체적인 클래스 여야합니다. ?)보기를 반환하기 전에 동일한 값으로 설정된 dataSource가 있습니다.

이 모든 작업을 수행하기위한 정확한 코드는 무엇입니까? 감사!

답변

1

코드를 RenderingContext.Current.Rendering.DataSource과 같이 정적 종속성에 밀접하게 연결하면 코드를 독립적으로 테스트하기가 어려워 질 수 있습니다.

RenderingContext에 대한 정적 액세스를 캡슐화하는 래퍼를 만드는 것이 좋습니다. 그런 다음 컨트롤러를 업데이트 할 GitHub의

Glass.Mapper 저장소에
public interface IRenderingContext { 
    string GetDataSource(); 
} 

//... 

using Sitecore.Mvc.Presentation; 

public class RenderingContextWrapper : IRenderingContext { 
    public string GetDataSource(){ 
     return RenderingContext.CurrentOrNull.Rendering.DataSource; 
    } 
} 

을 발견 코드 예제를 참조하면 명시 적으로 생성자 삽입을 통해 그 추상화에 의존하는

public class EmailArticleController : GlassController { 
    private readonly IEmailArticleBusiness businessLogic; 
    private readonly IRenderingContext renderingContext; 

    public EmailArticleController(IEmailArticleBusiness businessLogic, IRenderingContext renderingContext) { 
     this.businessLogic = businessLogic; 
     this.renderingContext = renderingContext; 
    } 

    public ActionResult Index() { 
     var model = businessLogic.FetchPopulatedModel; 
     var datasourceId = renderingContext.GetDataSource(); 
     businessLogic.SetDataSourceID(datasourceId); 
     return View("~/Views/EmailCampaign/EmailArticle.cshtml", model); 
    } 
} 

이제 할 모든 종속성을 조롱 할 수 있습니다 컨트롤러를 고립 상태로 테스트 할 수있다.

[TestClass] 
public class UnitTest1 { 
    [TestMethod] 
    public void Test_EmailArticleController_With_RenderingContext() { 
     //Arrange 
     var businessLogicFake = new Mock<IEmailArticleBusiness>(); 

     var model = new EmailArticleViewModel() { 
      ArticleControl = new Article_Control() { }, 
      Metadata = new Metadata() { } 
     }; 

     businessLogicFake.Setup(x => x.FetchPopulatedModel).Returns(model); 

     var datasourceId = "fake_datasourceId"; 
     var renderingContext = Mock.Of<IRenderingContext>(_ => _.GetDataSource() == datasourceId); 

     var controllerUnderTest = new EmailArticleController(businessLogicFake.Object, renderingContext); 

     //Act 
     var result = controllerUnderTest.Index() as ViewResult; 

     //Assert 
     Assert.IsNotNull(result); 
     businessLogicFake.Verify(_ => _.SetDataSourceID(datasourceId), Times.AtLeastOnce()); 
    } 
} 

생산 코드는 분명히 의존성의 런타임 해결을위한 당신의 DI 컨테이너와 추상화와 구현을 등록 할 것입니다.

+0

답변 해 주셔서 감사합니다. 그것은 매력처럼 일했습니다! "RenderingContext.Current.Rendering.DataSource와 같은 정적 종속성에 코드를 밀접하게 연결하면 코드를 독립적으로 테스트 할 수 없으므로 RenderingContext에 대한 정적 액세스를 캡슐화하는 래퍼를 만드는 것이 가장 좋습니다." 단위 테스트를 위해 EmailArticleController() 클래스에 절대적으로 코드를 추가해야하는 다른 이유가 있습니까? 생성자에 아무것도 보내지 않고 원래의 Index() 메서드를 단위 테스트 할 수있는 방법이 없다는 것을 의미합니까? – user3034243

+1

@ user3034243 더 많은 디자인이 필요합니다. 정적 종속성에 대한 제어권이 없으므로 정상 작동을 벗어난 경우 필요할 때 초기화 방법을 제어 할 수 없습니다. 자신이 소유하지 않은 코드를 제어 할 수 없기 때문에 별도로 테스트하기가 어렵습니다. SOLID와 같은 주제를 읽으면 유지 관리가 쉬운 깨끗한 코드를 디자인하는 것과 관련하여 테스트를 포함하는 방법을 더 잘 이해할 수 있습니다. – Nkosi

+0

@ user3034243 나는 가능한 다른 방법이있을 것이라고 거의 확신하지만, 쉽게 시작할 수있는 디자인을 쉽게 할 수있을 때마다 사냥을한다. 나는 매번보다 깨끗한 디자인을 선택한다. – Nkosi