How to Unit Test a GlassController Action which Uses Sitecore.Mvc.Presentation.RenderingContext

728 Views Asked by At

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!

1

There are 1 best solutions below

5
On BEST ANSWER

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.