Search code examples
c#jsonasp.net-mvcunit-testingjsonresult

How to unit test an Action method that takes Array of an Object and returns JsonResult in ASP.Net MVC


I am trying to write a unit test in Visual C# Unit Test project.

I pass an empty Array of Frame class and that returns a JSON object.

    [HttpPost]
    public JsonResult SubmitBowlingScore(Frame[] frames)
    {
        int totalScore= 0;
        var objScore = new EngineService();

        for (int i = 0; i < frames.Length; i++)
        {
            Boolean wasSpare = false;

            if (i > 0 && objScore.IsSpare(frames[i-1]))
            {
                wasSpare = true;
            }
            totalScore += objScore.CalculateScore(frames[i], wasSpare);
        }

        return Json("{\"score\":"+ totalScore + "}");
    }

Hoping to test with the following entry: But no idea how!!!

 [{""1stRoll"":2,""2ndRoll"":2 ,""3rdRoll"":0},
  {""1stRoll"":4,""2ndRoll"":8 ,""3rdRoll"":0},
  {""1stRoll"":6,""2ndRoll"":2 ,""3rdRoll"":0}];

Any help/idea/suggestion would be appreciated for the following unit test. How SubmitBowlingScore() would take the Frame [] data as a parameter?

    [TestMethod]
    public void SubmitBowlingScore()
    {
        //Arrange
        HomeController controller = new HomeController();
        //Act
        JsonResult result = controller.SubmitBowlingScore(**What goes here???**) as JsonResult;

        //Assert
        Assert.IsNotNull(JsonResult, "No JsonResult returned from action method.");
        Assert.AreEqual(@"{[{""1stRoll"":""2"",""2ndRoll"":2 ,""3rdRoll"":0},{""1stRoll"":""2"",""2ndRoll"":8 ,""3rdRoll"":0},{""1stRoll"":""6"",""2ndRoll"":2 ,""3rdRoll"":0}],""Count"":3,""Success"":true}",
               result.Data.ToString());
    }

Solution

  • You are mixing your business logic with your presentation logic. You should move the entire body of your controller method to an object that calculates the score based on the frames. Once you've got that, there's nothing to test in the controller (unless you don't trust the MVC framework..)

    My rendition of your Frame model:

    public class Frame
    {
        public int FirstRoll { get; set; }
        public int SecondRoll { get; set; }
        public int ThirdRoll { get; set; }
    }
    

    Here is the business logic as an extension. You might want to break this out into its own class or possibly make it a member of your EngineService class.

    public static class FrameExtensions
    {
        public static int SumFrameScores(this Frame[] frames)
        {
            //break out early if no frames have been recorded
            if (frames.Length == 0) return 0;
    
            int totalScore = 0;
            var objScore = new EngineService();
    
            for (int i = 0; i < frames.Length; i++)
            {
                bool wasSpare = objScore.IsSpare(frames[i - 1]);
                totalScore += objScore.CalculateScore(frames[i], wasSpare);
            }
    
            return totalScore;
        }
    }
    

    When you're testing, you can test directly against your C# classes / types so you don't need to be worried about JSON/presentation layer data translation.

    [TestMethod]
    public void SubmitBowlingScore()
    {
        //Arrange
        var frames = new Frame[]
        {
                new Frame {FirstRoll = 2, SecondRoll = 2, ThirdRoll = 0},
                new Frame {FirstRoll = 2, SecondRoll = 6, ThirdRoll = 0},
                new Frame {FirstRoll = 0, SecondRoll = 9, ThirdRoll = 0}
        };
        //Act
        var score = frames.SumFrameScores();
    
        //Assert
        Assert.AreEqual(21, score);
    }
    

    Finally, your controller is reduced to the following:

    [HttpPost]
    public JsonResult SubmitBowlingScore(Frame[] frames)
    {
        var finalScore = frames.SumFrameScores();
        return Json(new { score = finalScore });
    }