Reputation: 71
I am implemeting integration testing using xunit and
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.0" />
following this tutorial: https://dotnetcorecentral.com/blog/asp-net-core-web-api-integration-testing-with-xunit/
When I run my test I get this message
String reference not set to an instance of a String
IntegrationTests.Categories.TestAdd:
Outcome: Failed
Error Message:
System.ArgumentNullException : String reference not set to an instance of a String.Parameter name: s
Stack Trace:
at System.Text.Encoding.GetBytes(String s)
at Dopshop.Startup.ConfigureServices(IServiceCollection services) in /Users/josueaceves/Desktop/dopshop/Startup.cs:line 114
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
at Microsoft.AspNetCore.Hosting.Internal.WebHost.Initialize()
at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
at Microsoft.AspNetCore.TestHost.TestServer..ctor(IWebHostBuilder builder, IFeatureCollection featureCollection)
at IntegrationTests.TestClientProvider..ctor() in /Users/josueaceves/Desktop/dopshop/IntegrationTests/TestClientProvider.cs:line 17
at IntegrationTests.Categories.TestAdd() in /Users/josueaceves/Desktop/dopshop/IntegrationTests/Categories.cs:line 31
--- End of stack trace from previous location where exception was thrown ---IntegrationTests.Categories.TestGetAll:
Outcome: Failed
Error Message:
System.ArgumentNullException : String reference not set to an instance of a String.Parameter name: s
Stack Trace:
at System.Text.Encoding.GetBytes(String s)
at Dopshop.Startup.ConfigureServices(IServiceCollection services) in /Users/josueaceves/Desktop/dopshop/Startup.cs:line 114
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
at Microsoft.AspNetCore.Hosting.Internal.WebHost.Initialize()
at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
at Microsoft.AspNetCore.TestHost.TestServer..ctor(IWebHostBuilder builder, IFeatureCollection featureCollection)
at IntegrationTests.TestClientProvider..ctor() in /Users/josueaceves/Desktop/dopshop/IntegrationTests/TestClientProvider.cs:line 17
at IntegrationTests.Categories.TestGetAll() in /Users/josueaceves/Desktop/dopshop/IntegrationTests/Categories.cs:line 19
--- End of stack trace from previous location where exception was thrown ---Total tests: 2. Passed: 0. Failed: 2. Skipped: 0
this is at the startup file line 114:
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII
.GetBytes(this.Configuration.GetSection("AppSettings:Token").Value)),
ValidateIssuer = false,
ValidateAudience = false
};
I have my 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();
}
}
}
And my two integration tests
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);
}
}
}
}
Does anyone have an idea what's going on? I'm new to testing in .NET Core. If anyone could point me in the right direction it would be highly appreciated.
<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" />
this is the startup file
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();
}
}
}
this is my Program.cs file
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");
}
}
Upvotes: 0
Views: 1206
Reputation: 71
I found out that my problem was in the configuration of the WebHost. I simply mimicked what I was already doing in my Program.cs file.
public TestClientProvider()
{
server = new TestServer(WebHost.CreateDefaultBuilder().UseStartup<Startup>());
Client = server.CreateClient();
}
Upvotes: 1