How to Unit Test a GlassController Action which Us

2019-08-29 23:56发布

问题:

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 "ArticleController" controller's Index() action method:

public class ArticleController : GlassController
{
    public override ActionResult Index()
    {
        // If a redirect has been configured for this Article, then redirect to new location.
        if (Sitecore.Context.Item.Fields[SitecoreFieldIds.WTW_REDIRECT_TO] != null && !string.IsNullOrEmpty(Sitecore.Context.Item.Fields[SitecoreFieldIds.WTW_REDIRECT_TO].Value))
        {
            var link = (LinkField)Sitecore.Context.Item.Fields[SitecoreFieldIds.WTW_REDIRECT_TO];
            if (link != null)
            {
                if (link.IsInternal)
                {
                    return Redirect(Sitecore.Links.LinkManager.GetItemUrl(link.TargetItem));
                }
                else
                {
                    return Redirect(link.Url);
                }
            }
        }

        var model = new ArticleBusiness().FetchPopulatedModel;

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

    //below is alternative code I wrote for mocking and unit testing the logic in above Index() function
    private readonly IArticleBusiness _businessLogic;
    public ArticleController(IArticleBusiness businessLogic)
    {
        _businessLogic = businessLogic;
    }
    public ActionResult Index(int try_businessLogic)
    {
        // How do we replicate the logic in the big if-statement in above "override ActionResult Index()" method?

        var model = _businessLogic.FetchPopulatedModel;

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

This is what I have in my unit testing class:

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

        var model = new ArticleViewModel()
        {
            ArticleType = "Newsletter",
            ShowDownloadButton = true
        };

        businessLogicFake.Setup(x => x.FetchPopulatedModel).Returns(model);
        //How do I also mock the Sitecore.Context.Item and send it into the constructor, if that's the right approach?

        ArticleController controllerUnderTest = new ArticleController(businessLogicFake.Object);

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

        //Assert
        Assert.IsNotNull(result);
        Assert.IsNotNull(result.Model);
    }
}

Basically I want to mock a Sitecore.Context.Item, which has a "LinkField" value (referred to as "SitecoreFieldIds.WTW_REDIRECT_TO" above), somehow send it into the controller, and perform the same exact logic as the big if-statement in our original "public override ActionResult Index()" method.

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

回答1:

You are coupling your code/logic to static classes that make it difficult to test in isolation. You are also trying to mock code you have no control over.

Encapsulate the desired functionality behind abstraction you control.

public interface IArticleRedirectService {
    Url CheckUrl();
}

public class ArticleRedirectionService : IArticleRedirectionService {
    public Url CheckUrl() {            
        if (Sitecore.Context.Item.Fields[SitecoreFieldIds.WTW_REDIRECT_TO] != null && 
            !string.IsNullOrEmpty(Sitecore.Context.Item.Fields[SitecoreFieldIds.WTW_REDIRECT_TO].Value)) {
            var link = (LinkField)Sitecore.Context.Item.Fields[SitecoreFieldIds.WTW_REDIRECT_TO];
            if (link != null) {
                if (link.IsInternal) {
                    return Sitecore.Links.LinkManager.GetItemUrl(link.TargetItem);
                } else {
                    return link.Url;
                }
            }
        }
        return null;
    }
}

The controller would explicitly depend on the service via constructor injection.

public class ArticleController : GlassController {            
    private readonly IArticleBusiness businessLogic;
    private readonly IArticleRedirectionService redirect;

    public ArticleController(IArticleBusiness businessLogic, IArticleRedirectionService redirect) {
        this.businessLogic = businessLogic;
        this.redirect = redirect;
    }

    public ActionResult Index() {
        // If a redirect has been configured for this Article, 
        // then redirect to new location.
        var url = redirect.CheckUrl();
        if(url != null) {
            return Redirect(url);
        }
        var model = businessLogic.FetchPopulatedModel;    
        return View("~/Views/EmailCampaign/EmailArticle.cshtml", model);
    }
}

The code now has the flexibility to mock dependencies in isolation for unit tests with Moq or any other framework.



回答2:

I highly recommend you to use Sitecore.FakeDb for such purpose which is unit testing framework for Sitecore. So in short words mocking of context item will look like that:

[TestCase]
public void FooActionResultTest()
{
    // arrange
    var itemId = ID.NewID;
    using (var db = new Db
    {
        new DbItem("Some Item", itemId)
        {
            new DbField(SitecoreFieldIds.WTW_REDIRECT_TO) { Value = "{some-raw-value}" }
        }
    })
    {   
        // act  
        Sitecore.Context.Item = db.GetItem(itemId);

        // assert
        Sitecore.Context.Item[SitecoreFieldIds.WTW_REDIRECT_TO].Should().Be("{some-raw-value}");
    }
}