首页 > 解决方案 > ASP.NET Core TestHost 集成测试“System.ArgumentNullException:字符串引用未设置为字符串的实例。”

问题描述

我正在使用 xunit 和

<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.0" />

按照本教程: https ://dotnetcorecentral.com/blog/asp-net-core-web-api-integration-testing-with-xunit/

当我运行测试时,我收到此消息

字符串引用未设置为字符串的实例

IntegrationTests.Categories.TestAdd:
结果:失败
错误消息:
System.ArgumentNullException:字符串引用未设置为字符串的实例。

参数名称:s

堆栈跟踪:

在 System.Text.Encoding.GetBytes(String s)
在 Dopshop.Startup.ConfigureServices(IServiceCollection services) 在 /Users/josueaceves/Desktop/dopshop/Startup.cs:line 114
--- 堆栈跟踪从先前位置结束异常被抛出 ---在 Microsoft.AspNetCore.Hosting.Internal.WebHost.Initialize() 的 Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices ()的
Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services) 。 Microsoft.AspNetCore.TestHost.TestServer..ctor 上的AspNetCore.Hosting.WebHostBuilder.Build() (IWebHostBuilder 生成器,IFeatureCollection featureCollection)




在 /Users/josueaceves/Desktop/dopshop/IntegrationTests/TestClientProvider.cs
中的 IntegrationTests.TestClientProvider..ctor():/Users/josueaceves/Desktop/dopshop/IntegrationTests/ Categories.cs 中的 IntegrationTests.Categories.TestAdd() 中的第 17 行:第 31 行
--- 从先前抛出异常的位置结束堆栈跟踪 ---

IntegrationTests.Categories.TestGetAll:
结果:失败
错误消息:
System.ArgumentNullException:字符串引用未设置为字符串的实例。

参数名称:s

堆栈跟踪:

在 System.Text.Encoding.GetBytes(String s)
在 Dopshop.Startup.ConfigureServices(IServiceCollection services) 在 /Users/josueaceves/Desktop/dopshop/Startup.cs:line 114
--- 堆栈跟踪从先前位置结束异常被抛出 ---在 Microsoft.AspNetCore.Hosting.Internal.WebHost.Initialize() 的 Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices ()的
Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services) 。 Microsoft.AspNetCore.TestHost.TestServer..ctor 上的AspNetCore.Hosting.WebHostBuilder.Build() (IWebHostBuilder 生成器,IFeatureCollection featureCollection)




在 /Users/josueaceves/Desktop/dopshop/IntegrationTests/TestClientProvider.cs
中的 IntegrationTests.TestClientProvider..ctor():/Users/josueaceves/Desktop/dopshop/IntegrationTests/ Categories.cs 中的 IntegrationTests.Categories.TestGetAll() 中的第 17 行:第 19 行
--- 从先前抛出异常的位置结束堆栈跟踪 ---

总测试:2。通过:0。失败:2。跳过:0

这是在启动文件第 114 行:

TokenValidationParameters validationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII
                    .GetBytes(this.Configuration.GetSection("AppSettings:Token").Value)),
                ValidateIssuer = false,
                ValidateAudience = false
            };

我有我的ClientTestProvider

using System;
using System.Net.Http;
using Dopshop;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;

namespace IntegrationTests
{
    public class TestClientProvider
    {
        private TestServer server;
        public HttpClient Client { get; private set; }

        public TestClientProvider()
        {

            server = new TestServer(new WebHostBuilder().UseStartup<Startup>());

            Client =  server.CreateClient();
        }
    }
}

还有我的两个集成测试

using System.Net;
using System.Threading.Tasks;
using Contracts.v1;
using Xunit;
using FluentAssertions;
using System.Net.Http;
using Newtonsoft.Json;
using System.Text;
using Dopshop.Models;

namespace IntegrationTests
{
    public class Categories
    {
        [Fact]
        public async Task TestGetAll()
        {
            using (var client = new TestClientProvider().Client)
            {
                var response = await client.GetAsync(ApiRoutes.Categories.GetAll);
                response.EnsureSuccessStatusCode();

                response.StatusCode.Should().Be(HttpStatusCode.OK);
            }
        }

        [Fact]
        public async Task TestAdd()
        {
            using (var client = new TestClientProvider().Client)
            {
                var response = await client.PostAsync(ApiRoutes.Categories.Add
                    , new StringContent(
                        JsonConvert.SerializeObject(new Category(){Name = "nike Shoes", Description = "shoe category", Sequence = 2, ShopId = 3}), 
                        Encoding.UTF8, 
                        "application/json"));

                response.EnsureSuccessStatusCode();

                response.StatusCode.Should().Be(HttpStatusCode.OK);
            }
        }
    }
} 

有谁知道发生了什么?我是 .NET Core 测试的新手。如果有人能指出我正确的方向,将不胜感激。

<PackageReference Include="FluentValidation.AspNetCore" Version="9.1.1"/>
<PackageReference Include="Microsoft.AspNetCore.App"/>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.1.1"/>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="7.0.0"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.4.0"/>
<PackageReference Include="CloudinaryDotNet" Version="1.11.0"/>
<PackageReference Include="xunit" Version="2.4.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0"/>
<PackageReference Include="Moq" Version="4.14.5"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1"/>
<PackageReference Include="SendGrid" Version="9.21.0"/>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.0" />
<PackageReference Include="FluentAssertions" Version="5.10.3" /> 

这是启动文件

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Authorization;
using AutoMapper;
using Data;
using Filters;
using FluentValidation.AspNetCore;
using Dopshop.Models;
using Dopshop.Services.Email;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Repositories.Implementations;
using Repositories.Interfaces;
using Services.Email;
using Services.Photos;
using Settings;
using Microsoft.AspNetCore.Http;
using static Contracts.v1.Validation.Validators;
using Newtonsoft.Json.Linq;
using System.Net;
using Services.Authentication;

namespace Dopshop
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<DopshopContext>(x => x.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
            
            services.AddMvc(options => {
                options.Filters.Add<ValidationFilter>();
            })
                .AddJsonOptions(options => {
                    options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                })
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
                .AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<CreateShopValidator>());;
            
            services.Configure<ApiBehaviorOptions>(options =>
            {
                options.SuppressModelStateInvalidFilter = true;
            });
            
            services.AddCors(options => {
                options.AddPolicy(name: "ProductionPolicy",
                    builder => {
                        builder.WithOrigins("https://hustlr.azurewebsites.net",
                                            "https://hustlr.cards", 
                                            "https://hustlr-39c77.web.app", 
                                            "https://hustlr-39c77.firebaseapp.com")
                                            .WithMethods("*")
                                            .WithHeaders("*");
                    });
            });

            services.AddSwaggerGen(option => {

                option.SwaggerDoc("v1", new OpenApiInfo{ Title  = "Dopshop API", Version = "v1"});

                option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Description = "JWT Authorization header using the bearer scheme",
                    Name = "Authorization",
                    In = Microsoft.OpenApi.Models.ParameterLocation.Header,
                    Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey,
                    Scheme = "Bearer"
                });
                
                option.AddSecurityRequirement(new OpenApiSecurityRequirement()
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer"
                            },
                            Scheme = "oauth2",
                            Name = "Bearer",
                            In = ParameterLocation.Header,

                        },
                        new List<string>()
                    }
                });
                
                // Set the comments path for the Swagger JSON and UI.
                var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
                option.IncludeXmlComments(xmlPath);
                });

            TokenValidationParameters validationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII
                    .GetBytes(this.Configuration.GetSection("AppSettings:Token").Value)),
                ValidateIssuer = false,
                ValidateAudience = false
            };

            services.AddAuthentication(options => 
                {
                    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(options => {
                    options.SaveToken = true;
                    options.TokenValidationParameters = validationParameters;
                    options.Events = new JwtBearerEvents();
                    options.Events.OnChallenge = context =>
                    {
                        // Skip the default logic.
                        context.HandleResponse();

                        var payload = new JObject
                        {
                            ["code"] = 401,
                            ["message"] = "Invalid JWT Token"
                        };

                        context.Response.ContentType = "application/json";
                        context.Response.StatusCode = 401;

                        return context.Response.WriteAsync(payload.ToString());
                    };
            });

            services.AddSingleton<TokenValidationParameters>(validationParameters);

            services.Configure<CloudinarySettings>(Configuration.GetSection("CloudinarySettings"));
            services.Configure<SendGridAPISettings>(Configuration.GetSection("SendGridSettings"));
            
            services.AddAutoMapper(typeof(Startup).Assembly);

            services.AddTransient<IShopRepository, ShopRepository>();
            services.AddTransient<IAuthRepository, AuthRepository>();
            services.AddTransient<IUserRepository, UserRepository>();
            services.AddTransient<IProductRepository, ProductRepository>();
            services.AddTransient<ICategoryRepository, CategoryRepository>();
            services.AddTransient<IProductCategoryRepository, ProductCategoryRepository>();
            services.AddTransient<IPhotoRepository, PhotoRepository>();
            services.AddTransient<IProductPhotosRepository, ProductPhotosRepository>();
            services.AddTransient<IReviewsRepository, ReviewsRepository>();
            services.AddTransient<ICategoryRepository, CategoryRepository>();
            services.AddTransient<IGenericRepository<ReviewPhoto>, GenericRepository<ReviewPhoto>>();
            services.AddTransient<IGenericRepository<ShopTheme>, GenericRepository<ShopTheme>>();
            services.AddTransient<IGenericRepository<Currency>, GenericRepository<Currency>>();
            services.AddTransient<IGenericRepository<Industry>, GenericRepository<Industry>>();
            services.AddTransient<IGenericRepository<ShopLocation>, GenericRepository<ShopLocation>>();
            services.AddTransient<IGenericRepository<ShopPaymentMethod>, GenericRepository<ShopPaymentMethod>>();
            services.AddTransient<IGenericRepository<PaymentMethodType>, GenericRepository<PaymentMethodType>>();
            services.AddTransient<IGenericRepository<SocialLink>, GenericRepository<SocialLink>>();
            services.AddTransient<IGenericRepository<LinkType>, GenericRepository<LinkType>>();
            services.AddTransient<IGenericRepository<Category>, GenericRepository<Category>>();
            services.AddTransient<IGenericRepository<ProductCategory>, GenericRepository<ProductCategory>>();
            services.AddTransient<IProductOptionRepository, ProductOptionRepository>();
            services.AddTransient<IProductOptionValueRepository, ProductOptionValueRepository>();
            services.AddTransient<IProductQuestionRepository, ProductQuestionRepository>();
            services.AddTransient<IProductVariationRepository, ProductVariationRepository>();
            services.AddTransient<IProductOptionValueVariationRepository, ProductOptionValueVariationRepository>();
            services.AddTransient<IRefreshTokenRepository, RefreshTokenRepository>();
            services.AddTransient<ISocialLinkRepository, ShopSocialLinkRepository>();
            services.AddTransient<IShopPaymentMethodsRepository, ShopPaymentMethodsRepository>();
            services.AddTransient<IReviewSentimentRepository, ReviewSentimentRepository>();
            services.AddTransient<IPasswordRecoveryRepository, PasswordRecoveryRepository>();
            services.AddTransient<IEmailService, SendGridEmailSender>();
            services.AddTransient<IPhotosService, CloudinaryPhotosService>();
            services.AddTransient<IAuthorizationHandlers, AuthorizationHandlers>();
            services.AddTransient<IAuthTokenGenerator, AuthTokenGenerator>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseCors(x => x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
                app.UseSwagger();
                app.UseSwaggerUI(c => { 
                    c.SwaggerEndpoint("/swagger/v1/swagger.json", "Hustlr API V1");
                    c.RoutePrefix = string.Empty;
                });

                app.UseDefaultFiles();
                app.UseStaticFiles();
            }
            else
            {
                app.UseCors("ProductionPolicy");
                app.UseHsts();
                //app.UseHttpsRedirection();
            }

            app.UseStatusCodePages(context => {
                var request = context.HttpContext.Request;
                var response = context.HttpContext.Response;
                response.ContentType = "application/json";
                if (response.StatusCode == (int)HttpStatusCode.Unauthorized)
                {
                    var payload = new JObject
                    {
                        ["code"] = 401,
                        ["message"] = "Unauthorized"
                    };
                    response.WriteAsync(payload.ToString());
                }

                return Task.CompletedTask;
            });
            
            app.UseAuthentication();
            app.UseMvc();
        }
    }
}

这是我的 Program.cs 文件

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Data;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Dopshop
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateWebHostBuilder(args).Build();

            using (var scope = host.Services.CreateScope())
            {
                var db = scope.ServiceProvider.GetService<DopshopContext>();
                db.Database.Migrate();
            }

            host.Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .UseUrls("http://*:5000");
    }
}

标签: c#asp.netasp.net-coreintegration-testingxunit.net

解决方案


我发现我的问题出在 WebHost 的配置中。我只是模仿了我在 Program.cs 文件中已经在做的事情。

    public TestClientProvider()
            {
                
                server = new TestServer(WebHost.CreateDefaultBuilder().UseStartup<Startup>());
                Client =  server.CreateClient();
            }


推荐阅读