1

I'm a sitecore developer and I want to create a sample sitecore helix unit testing project for testing out the logic you see in our "EmailArticleController" controller's Index() action method:

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

OK this is what I have in my unit testing class:

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

Basically I want to mock a rendering context, make sure it has a (string) DataSource value set to some value such as "/sitecore/home/...", I want to send it into the controller's constructor (if that's the proper way), call the Index(int ) method, and at the same time make sure my _businessLogic, which is only an interface in this case (should it be the concrete class instead?) has its dataSource set to that same value before returning the View.

What's the exact code for doing all of this? Thanks!

user3034243
  • 145
  • 1
  • 9

1 Answers1

1

Tightly coupling your code to static dependencies like RenderingContext.Current.Rendering.DataSource can make testing your code in isolation difficult.

I would suggest you create a wrapper to encapsulate the static access to the RenderingContext. Referring to code examples found at the Glass.Mapper repository on GitHub

public interface IRenderingContext {
    string GetDataSource();
}

//...

using Sitecore.Mvc.Presentation;

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

You would then update your controller to explicitly depend on that abstraction via constructor injection

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

You are now able to mock all dependencies to be able to test the controller in isolation.

[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());
    }
}

Your production code would obviously register the abstraction and implementation with your DI container for runtime resolution of dependencies.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Thank you very, very much for your answer!! It worked like a charm!! You stated that "Tightly coupling the code to static dependencies like RenderingContext.Current.Rendering.DataSource can make testing the code in isolation difficult. It's best to create a wrapper to encapsulate the static access to the RenderingContext." Are there any other reasons why we absolutely must add code to the EmailArticleController() class in order to unit test it? You're implying that there's no way we can unit test the original Index() method without sending in anything into a constructor, correct? – user3034243 Sep 15 '17 at 17:23
  • 1
    @user3034243 it is more a matter of design. You have no control of that static dependency, which means you also have no control of how it is initialized when needed outside of it's normal operations. It is that lack of control over code you do not own that makes it difficult to test in isolation. Read up on topic like SOLID and you will get a better understanding of how it relates to designing clean code that is easy to maintain, which would also include testing. – Nkosi Sep 16 '17 at 00:41
  • @user3034243 I am almost sure there may be another possible way but to hunt that down when you can easily just design it properly to begin with, I would choose the cleaner design every time. – Nkosi Sep 16 '17 at 00:42
  • thank you as always for your excellent and thorough answers! I will look into an alternative way of doing that. Meantime, would you mind taking a peek at my question at https://stackoverflow.com/questions/46247255/how-to-unit-test-a-glasscontroller-action-which-uses-sitecore-context-item/46276106 ? Thank you! – user3034243 Sep 18 '17 at 14:24
  • 1
    @user3034243 I took a look and it is basically the same as this one. You keep coupling your code to static classes. You should realy check out the repository and see if they have abstractions that would allow you to better design your code. – Nkosi Sep 18 '17 at 14:55