You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 05:05:05