WCF单元测试报错:无法加载System.ServiceModel.Web.WebOperationContext类型
WCF单元测试加载WebOperationContext失败的解决方法
问题背景
在为WCF应用编写单元测试时遇到异常:ServiceContract实现中,传入错误凭据返回401的逻辑在服务运行时正常,但单元测试执行时抛出System.TypeLoadException,提示无法从程序集System.ServiceModel.Web, Version=4.0.0.0加载System.ServiceModel.Web.WebOperationContext,已尝试匹配程序集版本但问题未解决。
业务实现代码
public GenericResponse<string> GetAuthenticationToken() { try { var authToken = Task.Run(async () => { var token = await _authenticationService.AuthenticationUser(); return token; }); return new GenericResponse<string>() { ErrorMessage = string.Empty, HasError = false, ResponseObject = authToken.Result }; } catch (Exception ex) when (ex.InnerException.Message.StartsWith("Invalid credentials passed")) { // Set the HTTP status code WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Unauthorized; return new GenericResponse<string> { HasError = true, ErrorMessage = "Invalid Credentials", ResponseObject = ex.InnerException.Message }; } catch (Exception ex) { var fault = new GenericErrorResponse { ErrorMessage = $"An unknown error occured while authenticating", ErrorCode = 500, Details = $"An error occured while getting authentication token: {ex.Message}. {ex.InnerException?.Message ?? string.Empty}" }; // Throw FaultException with custom fault throw new FaultException<GenericErrorResponse>(fault, new FaultReason(fault.ErrorMessage)); } }
单元测试代码
[TestMethod] public void GetAuthenticationToken() { mockAuthService.Setup(i => i.AuthenticationUser()) .Returns(() => Task.Run(() => VALID_TOKEN)); var expectedResponse = new GenericResponse<string>() { ErrorMessage = string.Empty, HasError = false, ResponseObject = VALID_TOKEN }; var awsWcfFileStorageService = new AwsWcfFileStorageService(mockAuthService.Object, mockS3FileService.Object); var actualResponse = awsWcfFileStorageService.GetAuthenticationToken(); actualResponse.Should().BeEquivalentTo(expectedResponse); }
异常信息
System.TypeLoadException: 'Could not load type 'System.ServiceModel.Web.WebOperationContext' from assembly 'System.ServiceModel.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.'
堆栈跟踪
at email_wcf_service.AwsWcfFileStorageService.GetAuthenticationToken() in D:\Projects\file-storage-windows-service\file-storage-wcf-service\FileStorageService.cs:line 89 at file_storage_wcf_service.Tests.FileStorageServiceTests.GetAuthenticationToken() in D:\Projects\file-storage-windows-service\file-storage-wcf-service.Tests\AwsWcfFileStorageServiceTests.cs:line 139 at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
问题原因
单元测试运行时没有WCF运行时环境,WebOperationContext.Current仅在WCF服务宿主的请求上下文中才会被初始化,直接实例化服务类调用方法时,该上下文不存在,最终触发类型加载异常。
解决方法
1. 抽象WebOperationContext依赖(推荐)
通过接口封装HTTP状态设置逻辑,让服务类依赖接口而非直接调用WebOperationContext,单元测试可轻松模拟该接口。
步骤1:定义抽象接口与实现类
public interface IHttpResponseContext { void SetUnauthorizedStatus(); } public class HttpResponseContext : IHttpResponseContext { public void SetUnauthorizedStatus() { WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Unauthorized; } }
步骤2:修改服务类依赖
// 服务类新增构造参数注入接口 private readonly IHttpResponseContext _httpResponseContext; public AwsWcfFileStorageService(IAuthenticationService authService, IS3FileService s3Service, IHttpResponseContext httpResponseContext) { _authenticationService = authService; _s3FileService = s3Service; _httpResponseContext = httpResponseContext; } // 异常块替换为接口调用 catch (Exception ex) when (ex.InnerException.Message.StartsWith("Invalid credentials passed")) { _httpResponseContext.SetUnauthorizedStatus(); return new GenericResponse<string> { HasError = true, ErrorMessage = "Invalid Credentials", ResponseObject = ex.InnerException.Message }; }
步骤3:单元测试中模拟接口
var mockHttpContext = new Mock<IHttpResponseContext>(); // 实例化服务时传入模拟对象 var awsWcfFileStorageService = new AwsWcfFileStorageService(mockAuthService.Object, mockS3FileService.Object, mockHttpContext.Object); // 测试错误凭据场景时,验证接口方法是否被调用 mockHttpContext.Verify(c => c.SetUnauthorizedStatus(), Times.Once);
2. 使用Microsoft Fakes模拟上下文(侵入性高)
通过Microsoft Fakes框架拦截WebOperationContext的静态调用,模拟上下文实例,但该方式依赖内部实现,维护成本较高:
using (ShimsContext.Create()) { ShimWebOperationContext.CurrentGet = () => { var mockContext = new ShimWebOperationContext(); mockContext.OutgoingResponseGet = () => new ShimOutgoingWebResponseContext(); return mockContext; }; // 执行测试逻辑 }
3. 改用集成测试策略
使用WCF自带的测试宿主(如WcfTestClient)或自宿主WCF服务进行测试,这种方式更接近真实运行环境,但不属于纯单元测试范畴。
额外优化建议
业务代码中使用Task.Run(async () => { ... }).Result会导致线程阻塞,建议将方法改为异步实现:
public async Task<GenericResponse<string>> GetAuthenticationToken() { try { var token = await _authenticationService.AuthenticationUser(); return new GenericResponse<string>() { ErrorMessage = string.Empty, HasError = false, ResponseObject = token }; } // 后续异常处理逻辑保持一致,异步方法中避免使用.Result }
内容的提问来源于stack exchange,提问作者Etonde Ngole
相关产品推荐
相关产品推荐

