How to Mock Session variables in ASP.net core unit

2020-04-08 11:47发布

问题:

How to Mock Session variables in ASP.net core unit testing project?

1) I have created a mock object of a session.

Mock mockHttpContext = new Mock(); Mock mockHttpContext = new Mock();Mock mockSession = new Mock().As();

2) Setup GetString() MEthod

mockSession.Setup(s => s.GetString("ModuleId")).Returns("1");

3) created controllerContext and assigned mockhttpContext object

controller.ControllerContext.HttpContext = mockHttpContext.Object;

4) Trying to read from a controller.

HttpContext.Session.GetString("ModuleId")

Whereas I get a null value of "ModuleId". Please help me to mock session GetString() method

Example:

        //Arrange
        //Note: Mock session 
        Mock<HttpContext> mockHttpContext = new Mock<HttpContext>();
        Mock<ITestSession> mockSession = new Mock<ISession>().As<ITestSession>();
        //Cast list to IEnumerable
        IEnumerable<string> sessionKeys = new string[] { };
        //Convert to list.
        List<string> listSessionKeys = sessionKeys.ToList();
        listSessionKeys.Add("ModuleId");
        sessionKeys = listSessionKeys;
        mockSession.Setup(s => s.Keys).Returns(sessionKeys);
        mockSession.Setup(s => s.Id).Returns("89eca97a-872a-4ba2-06fe-ba715c3f32be");
        mockSession.Setup(s => s.IsAvailable).Returns(true);
        mockHttpContext.Setup(s => s.Session).Returns(mockSession.Object);
     mockSession.Setup(s => s.GetString("ModuleId")).Returns("1");         

        //Mock TempData
        var tempDataMock = new Mock<ITempDataDictionary>();
        //tempDataMock.Setup(s => s.Peek("ModuleId")).Returns("1");

        //Mock service
        Mock<ITempServices> mockITempServices= new Mock<ITempServices>();
        mockITempServices.Setup(m => m.PostWebApiData(url)).Returns(Task.FromResult(response));

        //Mock Management class method
        Mock<ITestManagement> mockITestManagement = new Mock<ITestManagement>();
        mockITestManagement .Setup(s => s.SetFollowUnfollow(url)).Returns(Task.FromResult(response));

        //Call Controller method
        TestController controller = new TestController (mockITestManagement .Object, appSettings);
        controller.ControllerContext.HttpContext = mockHttpContext.Object;            
        controller.TempData = tempDataMock.Object;

        //Act
        string response = await controller.Follow("true");

        // Assert
        Assert.NotNull(response);
        Assert.IsType<string>(response);

回答1:

First create class Named mockHttpSession and inherit from ISession.

public class MockHttpSession : ISession
{
    Dictionary<string, object> sessionStorage = new Dictionary<string, object>();

    public object this[string name]
    {
        get { return sessionStorage[name]; }
        set { sessionStorage[name] = value; }
    }

    string ISession.Id
    {
        get
        {
            throw new NotImplementedException();
        }
    }

    bool ISession.IsAvailable
    {
        get
        {
            throw new NotImplementedException();
        }
    }

    IEnumerable<string> ISession.Keys
    {
        get { return sessionStorage.Keys; }
    }

    void ISession.Clear()
    {
        sessionStorage.Clear();
    }

    Task ISession.CommitAsync()
    {
        throw new NotImplementedException();
    }

    Task ISession.LoadAsync()
    {
        throw new NotImplementedException();
    }

    void ISession.Remove(string key)
    {
        sessionStorage.Remove(key);
    }

    void ISession.Set(string key, byte[] value)
    {
        sessionStorage[key] = value;
    }

    bool ISession.TryGetValue(string key, out byte[] value)
    {
        if (sessionStorage[key] != null)
        {
            value = Encoding.ASCII.GetBytes(sessionStorage[key].ToString());
            return true;
        }
        else
        {
            value = null;
            return false;
        }
    }        
}

Then use this session in actual controller:

     Mock<HttpContext> mockHttpContext = new Mock<HttpContext>();
        MockHttpSession mockSession = new MockHttpSession();           
        mockSession["Key"] = Value;
        mockHttpContext.Setup(s => s.Session).Returns(mockSession);
        Controller controller=new Controller();
        controller.ControllerContext.HttpContext = mockHttpContext.Object;


回答2:

I just ran into this problem recently and the only way out of it is to mock the function that the GetString method wraps, which is TryGetValue.

byte[] dummy = System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString());
_mockSession.Setup(x => x.TryGetValue(It.IsAny<string>(),out dummy)).Returns(true).Verifiable();

So you don't need to mock the call to the GetString method, you just mock what method that calls behind the scenes.



回答3:

Solution given by Pankaj Dhote is working. here is complete bug free code for ASP.NET CORE 2 MVC:

public class MockHttpSession : ISession
{
    Dictionary<string, object> sessionStorage = new Dictionary<string, object>();
    public object this[string name]
    {
        get { return sessionStorage[name]; }
        set { sessionStorage[name] = value; }
    }

    string ISession.Id
    {
        get
        {
            throw new NotImplementedException();
        }
    }
    bool ISession.IsAvailable
    {
        get
        {
            throw new NotImplementedException();
        }
    }
    IEnumerable<string> ISession.Keys
    {
        get { return sessionStorage.Keys; }
    }
    void ISession.Clear()
    {
        sessionStorage.Clear();
    }
    Task ISession.CommitAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        throw new NotImplementedException();
    }

    Task ISession.LoadAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        throw new NotImplementedException();
    }

    void ISession.Remove(string key)
    {
        sessionStorage.Remove(key);
    }

    void ISession.Set(string key, byte[] value)
    {
        sessionStorage[key] = value;
    }

    bool ISession.TryGetValue(string key, out byte[] value)
    {
        if (sessionStorage[key] != null)
        {
            value = Encoding.ASCII.GetBytes(sessionStorage[key].ToString());
            return true;
        }
        else
        {
            value = null;
            return false;
        }
    }
}

Then use this session in actual controller:

    Mock<HttpContext> mockHttpContext = new Mock<HttpContext>();
    MockHttpSession mockSession = new MockHttpSession();           
    mockSession["Key"] = Value;
    mockHttpContext.Setup(s => s.Session).Returns(mockSession);
    Controller controller=new Controller();
    controller.ControllerContext.HttpContext = mockHttpContext.Object;


回答4:

I used Pankaj Dhote's class for a mock ISession. I have to change one method from this:

bool ISession.TryGetValue(string key, out byte[] value)
{
    if (sessionStorage[key] != null)
    {
        value = Encoding.ASCII.GetBytes(sessionStorage[key].ToString());
        return true;
    }
    else
    {
        value = null;
        return false;
    }
}  

to the code below. Otherwise the reference to sessionStorage[key].ToString() returns the name of the type rather than the value in the dictionary.

    bool ISession.TryGetValue(string key, out byte[] value)
    {
        if (sessionStorage[key] != null)
        {
            value = (byte[])sessionStorage[key]; //Encoding.UTF8.GetBytes(sessionStorage[key].ToString())
            return true;
        }
        else
        {
            value = null;
            return false;
        }
    }


回答5:

At first i created implementation of ISession:

public class MockHttpSession : ISession
{
    readonly Dictionary<string, object> _sessionStorage = new Dictionary<string, object>();
    string ISession.Id => throw new NotImplementedException();
    bool ISession.IsAvailable => throw new NotImplementedException();
    IEnumerable<string> ISession.Keys => _sessionStorage.Keys;
    void ISession.Clear()
    {
        _sessionStorage.Clear();
    }
    Task ISession.CommitAsync(CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }
    Task ISession.LoadAsync(CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }
    void ISession.Remove(string key)
    {
        _sessionStorage.Remove(key);
    }
    void ISession.Set(string key, byte[] value)
    {
        _sessionStorage[key] = Encoding.UTF8.GetString(value);
    }
    bool ISession.TryGetValue(string key, out byte[] value)
    {
        if (_sessionStorage[key] != null)
        {
            value = Encoding.ASCII.GetBytes(_sessionStorage[key].ToString());
            return true;
        }
        value = null;
        return false;
    }
}

Secondly implemented it during controller definition:

private HomeController CreateHomeController()
        {
            var controller = new HomeController(
                mockLogger.Object,
                mockPollRepository.Object,
                mockUserRepository.Object)
            {
                ControllerContext = new ControllerContext
                {
                    HttpContext = new DefaultHttpContext() {Session = new MockHttpSession()}
                }
            };
            return controller;
        }