我想寫一個Web服務的單元測試。我創建我的測試項目,引用我的Web項目(不是服務引用,程序集引用),然後編寫一些代碼來測試Web服務 - 它們工作正常。但是,有些服務通過使用HttpContext.Current.User.Identity.IsAuthenticated
來確保用戶已登錄到Web應用程序。單元測試Web服務 - HttpContext
在測試的上下文中,沒有像HttpContext這樣的東西,所以測試總是失敗。這些Web服務應該如何進行單元測試?
我想寫一個Web服務的單元測試。我創建我的測試項目,引用我的Web項目(不是服務引用,程序集引用),然後編寫一些代碼來測試Web服務 - 它們工作正常。但是,有些服務通過使用HttpContext.Current.User.Identity.IsAuthenticated
來確保用戶已登錄到Web應用程序。單元測試Web服務 - HttpContext
在測試的上下文中,沒有像HttpContext這樣的東西,所以測試總是失敗。這些Web服務應該如何進行單元測試?
Here是一個相關的討論。
我停止直接引用HttpContext.Current
。並且使用這個類來代替:
public class HttpContextFactory
{
private static HttpContextBase m_context;
public static HttpContextBase Current
{
get
{
if (m_context != null)
return m_context;
if (HttpContext.Current == null)
throw new InvalidOperationException("HttpContext not available");
return new HttpContextWrapper(HttpContext.Current);
}
}
public static void SetCurrentContext(HttpContextBase context)
{
m_context = context;
}
}
,並在我們的代碼中使用HttpContextFactory.Current
代替HttpContext.Current
。
然後你在測試寫:
HttpContextFactory.SetCurrentContext(GetMockedHttpContext());
其中GetMockedHttpContext()是here,看起來像這樣:
private System.Web.HttpContextBase GetMockedHttpContext()
{
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
var session = new Mock<HttpSessionStateBase>();
var server = new Mock<HttpServerUtilityBase>();
var user = new Mock<IPrincipal>();
var identity = new Mock<IIdentity>();
context.Setup(ctx => ctx.Request).Returns(request.Object);
context.Setup(ctx => ctx.Response).Returns(response.Object);
context.Setup(ctx => ctx.Session).Returns(session.Object);
context.Setup(ctx => ctx.Server).Returns(server.Object);
context.Setup(ctx => ctx.User).Returns(user.Object);
user.Setup(x => x.Identity).Returns(identity.Object);
identity.Setup(id => id.IsAuthenticated).Returns(true);
identity.Setup(id => id.Name).Returns("test");
return context.Object;
}
它使用了一個名爲moq
在mocking framework您測試項目你必須添加對System.Web
和System.Web.Abstractions
的引用,其中HttpContextBase
已定義。
如果使用的是嘲諷,你可以在另一個類包裝這樣的邏輯:
interface IAuthenticator
{
bool IsAuthenticated();
}
並實現真正的一個:
,但在測試中,創建一個模擬,並返回true或假:
Mock<IAuthenticator> mock = new Mock<IAuthenticator>();
mock.Expect(x => x.IsAuthenticated()).Returns(true);
要絕對正確我認爲這應該是一個存根,而不是一個模擬。 – 2010-10-26 21:21:45
你的意思是我們需要一個存根而不是模擬嗎?我不同意,因爲基於認證或不認證,服務會有不同的表現,因此我們需要期望能夠返回。我不確定 - 也不那麼熱衷 - 存根和模擬的差異,但對我來說,這是一個模擬而不是存根。 – Aliostad 2010-10-26 21:25:31
如果打算測試IAuthenticator接口是否正確調用,那麼它應該是一個模擬。如果你想測試其他東西,它應該是一個存根。存根永遠不會導致測試失敗。他們只是爲了讓事情順利進行。無論如何,我想這取決於你的模擬框架。在犀牛嘲笑模擬和存根之間有一個微妙的區別:http://stackoverflow.com/questions/463707/what-are-the-differences-between-mocks-and-stubs-on-rhino-mocks – 2010-10-26 23:09:48
你可能會考慮取決於System.Web.Abstractions.HttpContextBase而不是你的依賴唱HttpContext.Current。 System.Web.Abstractions程序集有許多常用的ASP.NET Http *類已經包裝給你。它們遍佈ASP.NET MVC代碼。如果您使用IoC/DI框架,使用起來非常簡單。例如,在Ninject:
Bind<HttpContextBase>.ToMethod(x => new HttpContextWrapper(HttpContext.Current));
,然後在你的構造......
public class SomeWebService
{
private HttpContextBase _httpContext;
public SomeWebService(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
public void SomeOperationNeedingAuthorization()
{
IIdentity userIdentity = _httpContext.User.Identity;
if (!userIdentity.IsAuthenticated)
return;
// Do something here...
}
}
這是過於簡單的路,但我希望你的想法......至於Aliostad提到的,你可以很容易地模擬HttpContextBase使用Rhino Mocks或Moq等來測試SomeOperationNeedingAuthorization。
你也可能考慮使用商用模擬工具,如Typemock Isolator或Telerik的JustMock,它可以減輕使用/管理Http *抽象的負擔。 – 2010-10-26 21:44:10
我最後決定將財產上的Web服務:
Private mIdentity As System.Security.Principal.IIdentity
Public Property Identity() As System.Security.Principal.IIdentity
Get
If mIdentity Is Nothing Then mIdentity = HttpContext.Current.User.Identity
Return mIdentity
End Get
Set(ByVal value As System.Security.Principal.IIdentity)
mIdentity = value
End Set
End Property
在我的web服務方法
然後:
<WebMethod()> _
Public Function GetProject(ByVal projectId As Int32) As String
If Me.Identity.IsAuthenticated Then
'code here
End If
End Function
然後在我的測試(我使用RhinoMocks):
Dim mockery As New MockRepository()
Dim mockIdentity As System.Security.Principal.IIdentity = mockery.DynamicMock(Of System.Security.Principal.IIdentity)()
Dim projectService As New TeamDynamix.Enterprise.Web.Next.ProjectService()
projectService.Identity = mockIdentity
mockIdentity.Stub(Function(i As System.Security.Principal.IIdentity) i.IsAuthenticated).Return(True)
基於上述解決方案,我實現了在O2 Platform允許輕鬆使用這些嘲諷類的包裝類,例如這是我怎麼能寫,並從HttpRequest.InputStream讀
var mockHttpContext = new API_Moq_HttpContext();
var httpContext = mockHttpContext.httpContext();
httpContext.request_Write("<html><body>".line());
httpContext.request_Write(" this is a web page".line());
httpContext.request_Write("</body></html>");
return httpContext.request_Read();
看到這篇博客文章的更多細節:http://o2platform.wordpress.com/2011/04/05/mocking-httpcontext-httprequest-and-httpresponse-for-unittests-using-moq/
我也開始這樣做(用HttpContextFactory替換HttpContext),它確實有助於單元測試(我將這些功能包裝在一個API中,您可以在這裏看到http://o2platform.wordpress.com/2011/ 04/05/mocking-httpcontext-httprequest-and-httpresponse-for-unittests-using-moq /) – 2011-04-05 11:11:50
我沒有使用moq,所以這沒有讓我100%,但它是有幫助的。我看着Stephen Walther的假物件尋求幫助:http://stephenwalther.com/archive/2008/07/01/asp-net-mvc-tip-12-faking-the-controller-context.aspx – 2012-08-29 15:49:30