Srujana
Srujana

Reputation: 99

How to mock Session Object in asp net core

I am writing unit tests using moq framework. I am calling one method in base controller setsession() which will set session using SetString("userdata",object) and I have one more method getsession() which will get session.

var sessionMock = new Mock<ISession>();
sessionMock.Setup(s => s.GetString("userdata")).Returns(Object);--failing
sessionMock.Setup(s => s.SetString("userdata",object));--failing

I am getting the error in s.GetString and s.SetString.

As GetString and SetString are extension methods may be I need to use another way to handle it.

Can you please help me?

Upvotes: 6

Views: 5952

Answers (4)

Andre
Andre

Reputation: 1

Extending the solution of NKosi to be able to mock any type of object stored in session, first create extensionmethods GetObject and SetObject

public static class SessionExtensions
{
    public static T GetObject<T>(this ISession session, string key)
    {
        var data = session.GetString(key);
        if (data == null)
        {
            return default(T);
        }
        return JsonConvert.DeserializeObject<T>(data);
    }

    public static void SetObject(this ISession session, string key, object value)
    {
        session.SetString(key, JsonConvert.SerializeObject(value));
    }
}

Then create a TestHelper method like this:

public static void SetSession(Controller controller, string key = null, object testObject = null)
{
    controller.ControllerContext.HttpContext = new DefaultHttpContext();
    var mockSession = new Mock<ISession>();            
        
    if(testObject != null && key != null)
    {
        var value = Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(testObject));
        mockSession.Setup(s => s.Set(key, It.IsAny<byte[]>())).Callback<string, byte[]>((k, v) => value = v);
        mockSession.Setup(s => s.TryGetValue(key, out value)).Returns(true);
    }
    controller.HttpContext.Session = mockSession.Object;
}

Imagine a controller which retreives a custom object from Session like this:

public IActionResult TestSessionObjects()
{
    .....
    var test = HttpContext.Session.GetObject<CustomObject>("KeyInSession");
    .....
    return View();
}

Now you can create a test:

[Fact]
public async void TestSessionObjectsTest()
{
    // Arrange                        
    var myController = new MyController();
    TestHelpers.SetSession(myController, "KeyInSession", new CustomObject());

    // Act
    ViewResult result = await myController.TestSessionObjects() as ViewResult;

    // Assert
    ....
}

Now when in your method under test GetObject is called, it will return the object created in the Arrange part.

HttpContext.Session.GetObject<CustomObject>("KeyInSession")

This will also work for e.g. int32. Set it up like:

[Fact]
public async void TestSessionObjectsTestInt()
{
    // Arrange                        
    var myController = new MyController();
    TestHelpers.SetSession(myController, "AnInt32", 2022);
    ....         
}

When in your method under test the GetObject is called 2022 will be returned:

HttpContext.Session.GetObject<int>("AnInt32")

Upvotes: 0

Nkosi
Nkosi

Reputation: 247088

According to ISession Extensions source code GetString and SetString are extension methods

public static void SetString(this ISession session, string key, string value)
{
    session.Set(key, Encoding.UTF8.GetBytes(value));
}

public static string GetString(this ISession session, string key)
{
    var data = session.Get(key);
    if (data == null)
    {
        return null;
    }
    return Encoding.UTF8.GetString(data);
}

public static byte[] Get(this ISession session, string key)
{
    byte[] value = null;
    session.TryGetValue(key, out value);
    return value;
}

You will need to mock ISession.Set and ISession.TryGetValue in order to let the extension methods execute as expected.

//Arrange
var sessionMock = new Mock<ISession>();
var key = "userdata";
var value = new byte[0];

sessionMock.Setup(_ => _.Set(key, It.IsAny<byte[]>()))
    .Callback<string, byte[]>((k,v) => value = v);

sessionMock.Setup(_ => _.TryGetValue(key, out value))
    .Returns(true);

Upvotes: 17

Emkay
Emkay

Reputation: 101

Thanks Nkosi for the great answer. In my case I had to deal with GetInt32 and SetInt32 extension methods in the unit test. As per the source code below is the implementation

public static void SetInt32(this ISession session, string key, int value)
        {
            var bytes = new byte[]
            {
                (byte)(value >> 24),
                (byte)(0xFF & (value >> 16)),
                (byte)(0xFF & (value >> 8)),
                (byte)(0xFF & value)
            };
            session.Set(key, bytes);
        }

public static int? GetInt32(this ISession session, string key)
        {
            var data = session.Get(key);
            if (data == null || data.Length < 4)
            {
                return null;
            }
            return data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
        }

so following what's mentioned by as answer I had to modify little bit as below to handle it.

 Mock<ISession> sessionMock = new Mock<ISession>();
        var key = "FY";
        int fy = 2020;
        var value =  new byte[]
        {
            (byte)(fy >> 24),
            (byte)(0xFF & (fy >> 16)),
            (byte)(0xFF & (fy >> 8)),
            (byte)(0xFF & fy)
        };

  sessionMock.Setup(_ => _.TryGetValue(key, out value)).Returns(true);

I couldn't find this anywhere so sharing here in case it is helpful for someone who are having same issue.

Upvotes: 2

Nicwin
Nicwin

Reputation: 89

here's my soultion to question

) create a new mock ISession & new mock HttpContext

//Arrange
var mockContext = new Mock<HttpContext>();
var mockSession = new Mock<ISession>();

) create session value

SomeClass sessionUser = new SomeClass() { Property1 = "MyValue" };
var sessionValue = JsonConvert.SerializeObject(sessionUser);
byte[] dummy = System.Text.Encoding.UTF8.GetBytes(sessionValue);

) setup mocks to return serialized obj

mockSession.Setup(x => x.TryGetValue(It.IsAny<string>(), out dummy)).Returns(true); //the out dummy does the trick
mockContext.Setup(s => s.Session).Returns(mockSession.Object);

) inject to mvc httpcontext (in my case a Page instead of a typical controller)

_classUnderTest.PageContext = new Microsoft.AspNetCore.Mvc.RazorPages.PageContext();
_classUnderTest.PageContext.HttpContext = mockContext.Object;

Now all is up and running, when i hit the session getvalue i receive that serializes object

Greez Nic

Upvotes: 1

Related Questions