问题描述
我正在使用 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)
在/Users/josueaceves/Desktop/dopshop/Startup.cs:line 114
中的Dopshop.Startup.ConfigureServices(IServiceCollection services)
--- 从上一个抛出异常的位置开始的堆栈跟踪结束---
在 Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection 服务)
在 Microsoft.AspNetCore.Hosting.Internal.WebHost.Ensureapplicationservices()
在 Microsoft.AspNetCore.Hosting.Internal.WebHost.Initialize()
在 Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
在 Microsoft.AspNetCore.TestHost.TestServer..ctor(IWebHostBuilder builder,IFeatureCollection featureCollection)
在/Users/josueaceves/Desktop/dopshop/IntegrationTests/TestClientProvider.cs:line 17
中的IntegrationTests.TestClientProvider..ctor()
在/Users/josueaceves/Desktop/dopshop/IntegrationTests/Categories.cs:line 31
中的IntegrationTests.Categories.TestAdd()
--- 从上一个抛出异常的位置开始的堆栈跟踪结束---
IntegrationTests.Categories.TestGetAll:
结果:失败
错误信息:
System.ArgumentNullException:未将字符串引用设置为字符串的实例。
参数名称:s
堆栈跟踪:
在 System.Text.Encoding.GetBytes(String s)
在/Users/josueaceves/Desktop/dopshop/Startup.cs:line 114
中的Dopshop.Startup.ConfigureServices(IServiceCollection services)
--- 从上一个抛出异常的位置开始的堆栈跟踪结束---
在 Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection 服务)
在 Microsoft.AspNetCore.Hosting.Internal.WebHost.Ensureapplicationservices()
在 Microsoft.AspNetCore.Hosting.Internal.WebHost.Initialize()
在 Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
在 Microsoft.AspNetCore.TestHost.TestServer..ctor(IWebHostBuilder builder,IFeatureCollection featureCollection)
在/Users/josueaceves/Desktop/dopshop/IntegrationTests/TestClientProvider.cs:line 17
中的IntegrationTests.TestClientProvider..ctor()
在/Users/josueaceves/Desktop/dopshop/IntegrationTests/Categories.cs:line 19
中的IntegrationTests.Categories.TestGetAll()
--- 从上一个抛出异常的位置开始的堆栈跟踪结束---
总测试数: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,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();
}