c# - 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");
}
}
解决方案
我发现我的问题出在 WebHost 的配置中。我只是模仿了我在 Program.cs 文件中已经在做的事情。
public TestClientProvider()
{
server = new TestServer(WebHost.CreateDefaultBuilder().UseStartup<Startup>());
Client = server.CreateClient();
}
推荐阅读
- reactjs - React Material UI 响应式 Appbar
- google-play - 卡在处理更新一个多月
- python - Zillow API having issues with running basic commands
- .net - 如何获取资源文件的路径?
- node.js - 如何让 Typescript 转译我的 Sequelize 模型?
- linux - VS 代码:NPM 在命令行中运行良好,但“NPM 脚本”窗格获取“/bin/sh: 1: npm: not found”
- javascript - 按值而不是键对 Laravel pluck() 进行排序?
- apache-spark - 将数据加载到 Spark Dataframe 中,源中没有分隔符
- ruby-on-rails - Rails_admin 具有 has_many、belongs_to 模型关系的下拉菜单
- python - 可以在 Python 中计算多变量信号的相干矩阵而不使用循环吗?