How to set the value of a query string in test met

2020-08-20 11:16发布

问题:

I have the following controller action method and I am writing a unit test for this method

    try
    {
    if ( Session["token"] == null)
        {
            //checking whether the user has already given the credentials and got redirected by survey monkey by  checking the query string 'code'
            if (Request.QueryString["code"] != null)
            {
                string tempAuthCode = Request.QueryString["code"];
                Session["token"] = _surveyMonkeyService.GetSurveyMonkeyToken(ApiKey, ClientSecret, tempAuthCode, RedirectUri, ClientId);
            }
            else
            {
                //User coming for the first time directed to authentication page
                string redirectUrlToSurveyMonkeyAuthentication = _surveyMonkeyService.GetUrlToSurveyMonkeyAuthentication(RedirectUri, ClientId, ApiKey);
                return Redirect(redirectUrlToSurveyMonkeyAuthentication);
            }
        }    
        //User is in the same session no need for token again showing surveys without authentication
        var model = _surveyService.GetSurveys(User.Identity.Name);
        if (model.Count == 0)
            return View(CSTView.NoSurveyTracker.ToString());
        return View(CSTView.Index.ToString(), model);
    }
    catch (Exception e)
    {
        return DisplayErrorView(e);//Even this returns a redirect method 
    }

And Here is one of the unit Test which I have written for it,

    [Test]
    public void GetIndexPage_Returns_View_With_ValidToken()
    {
        var mockControllerContext = new Mock<ControllerContext>();
        var mockSession = new Mock<HttpSessionStateBase>();
        mockSession.SetupGet(s => s["SurveyMonkeyAccessToken"]).Returns(SampleToken);
        mockSession.SetupGet(c => c["code"]).Returns(SampleTempAuthCode);
        mockControllerContext.Setup(p => p.HttpContext.Session).Returns(mockSession.Object);
        _surveyTrackerController.ControllerContext = mockControllerContext.Object;
        _surveyServiceMock.Setup(x => x.GetSurveys(TestData.TestData.SampleUserName)).Returns(SurveyTrackerList);
        var result = _surveyTrackerController.GetIndexPage();
        Assert.IsInstanceOf(typeof(ActionResult), result);
        Assert.AreEqual(((ViewResult)result).ViewName, "expected");
    }

When I am trying to run the test its throwing error: Object reference not set to an instance of object , and the line number shows to request.querystring , How to set the session variables in test methods, and Can anyone suggest me what is the proper way to check a controller action return type.

回答1:

Query String

You will also need to mock the query string in the HttpRequestBase object. For that you will need to build the object graph

ControllerContext -> HttpContextBase -> HttpRequestBase

As you are already mocking the ControllerContext of your controller instance, you can use the following code to add the mocked query string:

var queryString = new NameValueCollection { { "code", "codeValue" } };
var mockRequest = new Mock<HttpRequestBase>();
mockRequest.Setup(r => r.QueryString).Returns(queryString);
var mockHttpContext = new Mock<HttpContextBase>();
mockHttpContext.Setup(c => c.Request).Returns(mockRequest.Object);
mockControllerContext.Setup(c => c.HttpContext).Returns(mockHttpContext.Object);

Session

For the mocked session, use the same http context configured above to also return a mock session object:

var mockSession = new Mock<HttpSessionStateBase>();
mockHttpContext.Setup(c => c.Session).Returns(mockSession.Object);
//where mockHttpContext has been created in the code for the queryString above and setup to be returned by the controller context

Then you can set the values the way you did using SetupGet, or you can also use Setup as in

mockSession.Setup(s => s["token"]).Returns("fooToken")

If you want to verify that a value was set on the session, you can add something like this to your assert code:

mockSession.VerifySet(s => s["token"] = "tokenValue", Times.Once);

ActionResult types

What I usually do is to cast the result to the desired type using the as operator. It will return null if the conversion is not possible. So the assert may look like this:

ViewResult result = controller.Index() as ViewResult;

// Assert
Assert.IsNotNull(result);
Assert.AreEqual("fooView", result.ViewName);

Side Note

If you have many similar tests where the code is using the session and/or query string, there will be quite a few mock objects you need to create and configure on every test.

You could add a setup method to your test class (which is run before each test), and move there all the code that builds the object graph with the mocks. This way you have a fresh controller instance on every test method and the arrange part of every test will just need to setup the mocks behaviour and expectations.

For example, if you have this setup code in your test class:

private HomeController _homeController;
private Mock<HttpSessionStateBase> _mockSession;
private Mock<HttpRequestBase> _mockRequest;

[SetUp]
public void Setup()
{
    _mockRequest = new Mock<HttpRequestBase>();
    _mockSession = new Mock<HttpSessionStateBase>();
    var mockHttpContext = new Mock<HttpContextBase>();
    var mockControllerContext = new Mock<ControllerContext>();

    mockHttpContext.Setup(c => c.Request).Returns(_mockRequest.Object);
    mockHttpContext.Setup(c => c.Session).Returns(_mockSession.Object);
    mockControllerContext.Setup(c => c.HttpContext).Returns(mockHttpContext.Object);

    _homeController = new HomeController();
    _homeController.ControllerContext = mockControllerContext.Object;
}

The code on every test will be reduced to something like this:

[Test]
public void Index_WhenNoTokenInSession_ReturnsDummyViewAndSetsToken()
{
    // Arrange
    var queryString = new NameValueCollection { { "code", "dummyCodeValue" } };
    _mockSession.Setup(s => s["token"]).Returns(null);
    _mockRequest.Setup(r => r.QueryString).Returns(queryString);

    // Act
    ViewResult result = _homeController.Index() as ViewResult;

    // Assert
    Assert.IsNotNull(result);
    Assert.AreEqual("dummy", result.ViewName);
    _mockSession.VerifySet(s => s["token"] = "tokenValue", Times.Once);
}

[Test]
public void Index_WhenTokenInSession_ReturnsDefaultView()
{
    // Arrange
    _mockSession.Setup(s => s["token"]).Returns("foo");

    // Act
    ViewResult result = _homeController.Index() as ViewResult;

    // Assert
    Assert.IsNotNull(result);
    Assert.AreEqual(String.Empty, result.ViewName);
}

Where those tests are testing this dummy Index method

public ActionResult Index()
{
    if (Session["token"] == null)
    {
        if (Request.QueryString["code"] != null)
        {

            Session["token"] = "tokenValue";
            return View("dummy");
        }
    }
    return View();
}