Integration Testing with AutoMapper fails to initialise configuration - c#

Frameworks & Packages
.NETCoreApp 1.1
Xunit 2.2.0
AutoMapper 6.0.2
Microsoft.AspNetCore.TestHost 1.1.1
Microsoft.NET.Test.Sdk 15.0.0
Integration Test
public class ControllerRequestsShould
{
private readonly TestServer _server;
private readonly HttpClient _client;
public ControllerRequestsShould()
{
_server = new TestServer(new WebHostBuilder()
.UseContentRoot(Constants.apiProjectRoot)
.UseStartup<Startup>()
.UseEnvironment(Constants.testingEnvironment));
_client = _server.CreateClient();
_client.BaseAddress = new Uri(Constants.localHostUri);
}
[Fact]
public async Task CreateAnEntity()
{
// Arrange
var entityForCreationDto = new entityForCreationDto { Code = "00001", Name = "Entity One" };
var jsonContent = JsonConvert.SerializeObject(entityForCreationDto);
var stringContent = new StringContent(jsonContent);
stringContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
// Act
var response = await _client.PostAsync("/api/controller", stringContent);
response.EnsureSuccessStatusCode();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
Startup.cs
public class Startup
{
public IConfigurationRoot Configuration { get; }
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
// Add framework services
services.AddMvc(setupAction =>
{
setupAction.ReturnHttpNotAcceptable = true;
setupAction.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
setupAction.InputFormatters.Add(new XmlDataContractSerializerInputFormatter());
});
// Db context configuration
var connectionString = Configuration["ConnectionStrings:DefaultConnection"];
services.AddDbContext<YourContext>(options =>
{
options.UseSqlServer(connectionString);
});
// Register services for dependency injection
services.AddScoped<IYourRepository, YourRepository>();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IUrlHelper, UrlHelper>(implementationFactory =>
{
var actionContext =
implementationFactory.GetService<IActionContextAccessor>().ActionContext;
return new UrlHelper(actionContext);
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
loggerFactory.AddDebug(LogLevel.Information);
loggerFactory.AddNLog();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(appBuilder =>
{
appBuilder.Run(async context =>
{
var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
if (exceptionHandlerFeature != null)
{
var logger = loggerFactory.CreateLogger("Global exception logger");
logger.LogError(500,
exceptionHandlerFeature.Error,
exceptionHandlerFeature.Error.Message);
}
context.Response.StatusCode = 500;
await context.Response.WriteAsync("An unexpected fault happened. Try again later");
});
});
}
Mapper.Initialize(cfg =>
{
cfg.CreateMap<DataStore.Entities.Entity, Models.EntityDto>();
cfg.CreateMap<Models.EntityDto, DataStore.Entities.Entity>();
cfg.CreateMap<Models.EntityForCreationDto, DataStore.Entities.Entity>();
cfg.CreateMap<DataStore.Entities.Entity, Models.EntityForCreationDto>();
});
app.UseMvc();
}
Problem
The integration test fails after the controller method is invoked:
var response = await _client.PostAsync("/api/controller", stringContent);
It fails because AutoMapper has not been initialised.
The way I understood this was that since the TestServer has the UseStartup method, it should use all the services configured in the api Startup.cs class (the UseContentRoot is pointing to my api project root)
This clearly isn't happening. Could someone show me how I need to configure the TestServer so that the AutoMapper configuration is picked up correctly please?

You should specify the assembly in the ConfigureServices method :
var assembly = typeof(Program).GetTypeInfo().Assembly;
services.AddAutoMapper(assembly);
I'm using Automapper Modules, so the mapping config is picked up automatically by AutoMapper, but even then, you still need the above config.

Or just use this line
services.AddAutoMapper(typeof(Startup));
instead of
var assembly = typeof(Program).GetTypeInfo().Assembly;
services.AddAutoMapper(assembly);
which is more clear and clean in my opinion

Thank you, it's work for me. Additionally you can add configuration options like this.
var assembly = typeof(Program).GetTypeInfo().Assembly;
services.AddAutoMapper(cfg =>
{
cfg.AllowNullDestinationValues = true;
cfg.CreateMap<ApplicationUser, ApplicationUserView> ().IgnoreAllPropertiesWithAnInaccessibleSetter();}, assembly);

Related

After clone of ASP.NET Core Web API project on other computer receiving MediatR error

In my computer application works fine, but other computers receiving next error after trying handle any request:
Error constructing handler for request of type MediatR.IRequestHandler`2[Application.User.Register+Command,Application.User.User]. Register your handlers with the container. See the samples in GitHub for examples
connection string
I register MediatR with following line in my Startup.cs:
services.AddMediatR(typeof(List.Handler).Assembly);
My Startup class:
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<DataContext>(opt =>
{
opt.UseSqlite(Configuration.GetConnectionString("DefaultConnection"));
});
services.AddCors(opt =>
{
opt.AddPolicy("CorsPolicy", policy =>
{
policy.AllowAnyHeader().AllowAnyMethod().WithOrigins("http://localhost:3000");
});
});
services.AddMvc(opt =>
{
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
opt.Filters.Add(new AuthorizeFilter(policy));
})
.AddFluentValidation(cfg => cfg.RegisterValidatorsFromAssemblyContaining<Create>())
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
var builder = services.AddIdentityCore<AppUser>();
var identityBuilder = new IdentityBuilder(builder.UserType, builder.Services);
identityBuilder.AddEntityFrameworkStores<DataContext>();
identityBuilder.AddSignInManager<SignInManager<AppUser>>();
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["TokenKey"]));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateAudience = false,
ValidateIssuer = false
};
});
services.AddScoped<IJwtGenerator, JwtGenerator>();
services.AddScoped<IUserAccessor, UserAccessor>();
services.AddMediatR(typeof(List.Handler).Assembly);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<ErrorHandlingMiddleware>();
if (env.IsDevelopment())
{
//app.UseDeveloperExceptionPage();
}
else
{
// app.UseHsts();
}
// app.UseHttpsRedirection();
app.UseAuthentication();
app.UseCors("CorsPolicy");
app.UseMvc();
}
}
Problem is that i cannot reproduce same issue on my computer.
Problem was on another level:
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["TokenKey"]));
It was problem with reading Configuration. After fixing it, all works as designed.

Integration Tests for Web API with Azure Key Vault

I followed the tutorial here but it seems like the start up file does not recognize the appsetting.json file.
So when I run the actual project, the Iconfiguration have 7 properties.
But when I run the test, it only has one property.
So I was thinking maybe I missed something in the test method to configure the AppSetting.json file..
Here's my test method:
public class StudentServiceRequestsTest
{
private readonly TestServer _server;
private readonly HttpClient _client;
public IndividualServiceRequestsTest()
{
// Arrange
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
_client = _server.CreateClient();
}
[Fact]
public async Task GetStudentsByDeptShould()
{
try
{
//Act
var response = await _client.GetAsync("/api/department/200/students");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
//Assert
Assert.Equal("hi", responseString);
}
catch (Exception e)
{
throw;
}
}
Here's my startup class, apprently I added the json file which includes all the keys and secrets required in the Web API.
namespace CIS.API.Student
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; set; }
services.AddSingleton(Configuration);
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
app.UseMvc();
}
}
}
Anyone knows why it would happen?
After some research, including the tutorial: Integration tests in ASP.NET Core, I made it working.
Step 1: Copy the "appsetting.json" file to the integration test project.
Step 2: Modify the test class constructor to:
public class StudentServiceTest
{
private readonly TestServer _server;
private readonly HttpClient _client;
public StudentServiceTest()
{
var config = new ConfigurationBuilder().SetBasePath(Path.GetFullPath(#"..\..\..\..\Student.IntegrationTest"))
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
var builtConfig = config.Build();
config.AddAzureKeyVault(
$"https://{builtConfig["Vault"]}.vault.azure.net/",
builtConfig["ClientId"],
builtConfig["ClientSecret"]);
var Configuration = config.Build();
_server = new TestServer(WebHost.CreateDefaultBuilder()
.UseConfiguration(Configuration)
.UseStartup<Startup>());
_client = _server.CreateClient();
_client.BaseAddress = new Uri("http://localhost:xxxxx");
}
[Fact]
public async Task StudentShould()
{
try
{
//Act
var response = await _client.GetAsync("/api/getStudentByID/200");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
//Assert
Assert.Equal("bla bla", responseString);
}
catch (Exception e)
{
throw;
}
}
}

aspnet core integration test returning 404

I'm trying to implement the integration test for my app following the tutorial from: https://learn.microsoft.com/en-us/aspnet/core/testing/integration-testing
public class CreditCardApplicationShould
{
[Fact]
public async Task RenderApplicationForm()
{
var builder = new WebHostBuilder()
.UseContentRoot(#"C:\Users\usuario\source\repos\CreditCardApp\CreditCardApp")
.UseEnvironment("Development")
.UseStartup<CreditCardApp.Startup>()
.UseApplicationInsights();
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/Apply/Index");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
Assert.Contains("New Credit Card Application", responseString);
}
}
However, when I'm trying to run the integration test, it gives me the following error:
"Message: System.InvalidOperationException : The view 'Index' was not
found. The following locations were searched:
/Views/Apply/Index.cshtml /Views/Shared/Index.cshtml"
It seems to be a common problem when separating the integration test from the MVC application.
Here's the startup.cs too
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
CurrentEnvironment = env;
}
public IConfiguration Configuration { get; }
private IHostingEnvironment CurrentEnvironment { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddApplicationPart(typeof(ApplyController).GetTypeInfo().Assembly);
}
// 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.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
I've found some work arounds, that says to include the AddApplicationPart in the Startup.cs, but it's still not working.
I'm not sure if it's not working because I'm using .NET Core 2.0. I appreciate any tips.
I had similar problem like you have and I solved it by adding appsettings.json file path to WebHostBuilder(). I implemented like following.
var builder = new WebHostBuilder()
.UseContentRoot(#"C:\Users\usuario\source\repos\CreditCardApp\CreditCardApp")
.UseEnvironment("Development")
.UseStartup<CreditCardApp.Startup>()
.UseApplicationInsights()
.UseConfiguration(new ConfigurationBuilder()
.SetBasePath(YourProjectPath) // #"C:\Users\usuario\source\repos\CreditCardApp\CreditCardApp"
.AddJsonFile("appsettings.json")
.Build()
);

Inject dependency in the ConfigureServices

In my ASP.Net Core application I need to inject some dependencies (a repository, in my case) in the ConfigureServices method.
The problem is that method does not allow the use of multiple arguments to inject dependencies. What to do instead ?
Here is my code
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
// ...
services.AddSingleton<ITableRepositories, TableClientOperationsService>();
// Add framework services.
services.AddOpenIdConnect(options =>
{
// options.ClientId = ...
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = async context =>
{
var user = (ClaimsIdentity)context.Principal.Identity;
if (user.IsAuthenticated)
{
// ...
// vvv
// HERE, I need the ITableRepositories repository;
// vvv
var myUser = await repository.GetAsync<Connection>(userId);
// ...
}
return;
}
};
});
}
How can I inject the dependency here?
EDIT:
Following the Chris idea (bellow), that seem to work:
public class Startup
{
// private repository, used in ConfigureServices, initialized in Startup
ITableRepositories repository;
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
// ... etc etc
Configuration = builder.Build();
// init repository here
this.repository = new TableClientOperationsService();
}
You can access the service container via the HttpContext.RequestServices of the current context.
public void ConfigureServices(IServiceCollection services) {
services.AddOptions();
// ...
services.AddSingleton<ITableRepositories, TableClientOperationsService>();
// Add framework services.
services.AddOpenIdConnect(options => {
// options.ClientId = ...
options.Events = new OpenIdConnectEvents {
OnTicketReceived = async context => {
var user = (ClaimsIdentity)context.Principal.Identity;
if (user.IsAuthenticated) {
// ...
// get the ITableRepositories repository
var repository = context.HttpContext.RequestServices.GetService<ITableRepositories>();
var myUser = await repository.GetAsync<Connection>(userId);
// ...
}
return;
}
};
});
}
So technically you don't need access to the dependency within the ConfigureServices as the inline expression could be extracted into its own function.
public void ConfigureServices(IServiceCollection services) {
services.AddOptions();
// ...
services.AddSingleton<ITableRepositories, TableClientOperationsService>();
// Add framework services.
services.AddOpenIdConnect(options => {
// options.ClientId = ...
options.Events = new OpenIdConnectEvents {
OnTicketReceived = TicketReceived
};
});
}
private async Task TicketReceived(TicketReceivedContext context) {
var user = (ClaimsIdentity)context.Principal.Identity;
if (user.IsAuthenticated) {
// ...
// get the ITableRepositories repository
var repository = context.HttpContext.RequestServices.GetService<ITableRepositories>();
var myUser = await repository.GetAsync<Connection>(userId);
// ...
}
return;
}
Well, you can't. However, you can utilize the StartUp constructor to set one or more properties that you can utilize inside ConfigureServices. For example:
public StartUp(IHostingEnvironment env)
{
...
FooInstance = new Foo();
}
public IFoo FooInstance { get; }
public void ConfigureServices(IServiceCollection services)
{
// you can now use `Foo` here, without injecting it.
}
EDIT (based on code added to question)
In this particular scenario, especially since this is a singleton, you can simply create an instance manually and then bind to that instance instead of a generic type. For example:
var repository = new TableClientOperationsService();
services.AddSingleton<ITableRepositories>(repository);
Then, you can simply utilize this variable directly in the code below where you need it:
var myUser = await repository.GetAsync<Connection>(userId);

Getting Scope Validating error in Identity Server 4 using JavaScript Client in asp.net core

I am getting the below error while making a request to my Identity Server application from my Javascript Client Application.
fail: IdentityServer4.Validation.ScopeValidator[0]
Invalid scope: openid
I have made sure I add the scope in my Identity Server application.
Below is my code.
IdentityServer Application ( the Host)
Config.cs
public class Config
{
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api1","My API")
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "js",
ClientName = "javaScript Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://localhost:5003/callback.html" },
PostLogoutRedirectUris = { "http://localhost:5003/index.html" },
AllowedCorsOrigins = { "http://localhost:5003" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
};
}
}
Startup.cs
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseIdentityServer();
}
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
}
Web API Startup.cs
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsEnvironment("Development"))
{
// This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
builder.AddApplicationInsightsSettings(developerMode: true);
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddCors(option =>
{
option.AddPolicy("dafault", policy =>
{
policy.WithOrigins("http://localhost:5003")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
//this uses the policy called "default"
app.UseCors("default");
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:5000",
AllowedScopes = { "api1" },
RequireHttpsMetadata = false
});
app.UseApplicationInsightsRequestTelemetry();
app.UseApplicationInsightsExceptionTelemetry();
app.UseMvc();
}
}
While your client (application) is configured or allowed to request the openid resource (or scope), your identity server is not configured for the openid identity resource
You need to add it as an identity resource similar to how its done here and have a method that returns all your identity resources that you want to use like its done here.
In short add a new method to your Config.cs that looks like this:
public static List<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile() // <-- usefull
};
}
And then to your identityservers service container add your identity resource configuration like this:
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddInMemoryIdentityResources(Config.GetIdentityResources()); // <-- adding identity resources/scopes
In my particular case, this was caused by a missing call to .AddInMemoryApiScopes(), as shown by inspecting the return value of the below under the debugger (in particular, the Error and HttpStatusCode fields indicated invalid scope as you reported) from a simple console application.
await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { ... });
To resolve this, I added the below to method to my custom configuration class
public static IEnumerable<ApiScope> Scopes
{
get
{
return new List<ApiScope>
{
new ApiScope("my-scope-name", "Friendly scope name")
};
}
}
And then called this as such from within Startup.ConfigureServices()
services.AddIdentityServer()
.AddInMemoryApiResources(Configuration.Apis)
.AddInMemoryClients(Configuration.Clients)
.AddInMemoryApiScopes(Configuration.Scopes);

Categories

Resources