如何在ASP.net core单元测试项目中模拟会话变量?

6
如何在ASP.net Core单元测试项目中模拟会话变量?
1) 我已经创建了一个会话的模拟对象。
Mock<HttpContext> mockHttpContext = new Mock<HttpContext>();
Mock<ITestSession> mockSession = new Mock<ISession>().As<ITestSession>();

2) 设置 GetString() 方法

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

3) 创建了controllerContext并分配了mockhttpContext对象

controller.ControllerContext.HttpContext = mockHttpContext.Object; 

4) 尝试从控制器读取数据。

HttpContext.Session.GetString("ModuleId")

我遇到了“ModuleId”值为 null 的问题。请帮助我模拟 session GetString() 方法。

示例:

        //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);
 

展示被测试的方法。 - Nkosi
谢谢你,NKosi 的回复。 - Pankaj Dhote
我已经找到了解决这个问题的方法。我创建了一个模拟会话类,并从ISession继承。在这个模拟类中实现了ISession的所有方法,并使用这个类来存储会话变量。 - Pankaj Dhote
6个回答

10

首先创建一个名为mockHttpSession的类并继承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;
        }
    }        
}

然后在实际控制器中使用此会话:

     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;

我们可以使用这个session类来存储会话变量。例如:首先创建mockHttpSession对象,然后存储变量值。 - Pankaj Dhote
1
请不要使用这段代码,大家。ASCII.GetBytes(sessionStorage[key].ToString()); - 它正在使用不同的编码重新编码字节数组!我刚刚花了半天时间调试某人通过复制粘贴此代码编写的测试... 我一直在想“为什么我的字节数组会在会话中发生变化?” - Alex from Jitbit
我遇到了与@AlexfromJitbit类似的问题。在这里找到了解决方案,基本上将其切换为UTF8并修改一些逻辑以在TryGetValue方法中不进行双重转换:https://stackoverflow.com/questions/50277705/net-core-asp-net-controller-with-session-and-tests - Khoward

4

Pankaj Dhote提供的解决方案可行。这里是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;
        }
    }
}

然后在实际控制器中使用此会话:

    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;

3

最近我遇到了这个问题,唯一的解决办法是模拟GetString方法包装的TryGetValue函数。

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

因此,您无需模拟对GetString方法的调用,只需模拟其背后调用的方法即可。


我点赞了这个因为它指引了我正确的方向,但是它缺少一些实际使用所需的细节。 - michaela112358

3

我使用了Pankaj Dhote提供的类来模拟ISession。现在我需要修改其中一个方法:

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;
    }
}  

否则,对sessionStorage[key].ToString()的引用将返回类型名称而不是字典中的值。请参考下面的代码:
    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;
        }
    }

1

当我在使用.NET 5开发Blazor服务器时,想要测试一个使用ProtectedSessionStorage的组件时,以下步骤对我有效:

//add ProtectedSessionStorage

            testContext.Services.AddSingleton<Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.ProtectedSessionStorage>();



            //mock IJSRuntime

            var js = new Mock<IJSRuntime>();

            testContext.Services.AddSingleton(js.Object);



            //mock IDataProtector

            var mockDataProtector = new Mock<Microsoft.AspNetCore.DataProtection.IDataProtector>();

            mockDataProtector.Setup(sut => sut.Protect(It.IsAny<byte[]>())).Returns(Encoding.UTF8.GetBytes("protectedText"));

            mockDataProtector.Setup(sut => sut.Unprotect(It.IsAny<byte[]>())).Returns(Encoding.UTF8.GetBytes("originalText"));

            testContext.Services.AddSingleton(mockDataProtector.Object);



            //mock IDataProtectionProvider

            var mockDataProtectionProvider = new Mock<Microsoft.AspNetCore.DataProtection.IDataProtectionProvider>();

            mockDataProtectionProvider.Setup(s => s.CreateProtector(It.IsAny<string>())).Returns(mockDataProtector.Object);

            testContext.Services.AddSingleton(mockDataProtectionProvider.Object);

1
一开始,我创建了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;
    }
}

其次,在控制器定义过程中实施它:
private HomeController CreateHomeController()
        {
            var controller = new HomeController(
                mockLogger.Object,
                mockPollRepository.Object,
                mockUserRepository.Object)
            {
                ControllerContext = new ControllerContext
                {
                    HttpContext = new DefaultHttpContext() {Session = new MockHttpSession()}
                }
            };
            return controller;
        } 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接