I'm facing a strange issue since I created a view component in my app.
All my integration tests using an HttpClient start failing with response code "Internal Server Error".
Here the test configuration :
public class BaseTest<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
private readonly bool _hasUser;
private readonly HttpClient _client;
protected BaseTest(bool hasUser = false)
{
_hasUser = hasUser;
_client = CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false,
});
}
protected async Task<HttpResponseMessage> GetPageByPath(string path)
{
return await _client.GetAsync(path);
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
if (_hasUser)
{
services.AddScoped<IAuthenticationService, AuthenticationServiceStub>();
services.AddSingleton<IStartupFilter, FakeUserFilter>();
services.AddMvc(options =>
{
options.Filters.Add(new AllowAnonymousFilter());
options.Filters.Add(new AuthenticatedAttribute());
});
}
});
builder.ConfigureServices(services =>
{
// Create a new service provider.
ServiceProvider serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Build the service provider.
var sp = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database
// context (ApplicationDbContext).
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var logger = scopedServices
.GetRequiredService<ILogger<BaseTest<TStartup>>>();
}
});
}
}
}
And a usage example :
public class BasicTest : BaseTest<Startup>
{
public BasicTest() : base()
{
}
[Theory]
[InlineData("/")]
[InlineData("/Index")]
[InlineData("/Users/SignOut")]
[Trait("Category", "Integration")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Act
var response = await GetPageByPath(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
If you need the component code let me know.
I already rollback code to check when this start happening, and it's start after the commit with the new Component called in several pages.
Related
In my Xamarin app I get an exception on app startup on both Android and iOS.
"Cannot access a disposed object. Object name: 'MobileAuthenticatedStream'"
This seems to be happening after a background fetch has been run.
I am initializing my httpclientfactory like so
static void Init()
{
var host = new HostBuilder()
.ConfigureHostConfiguration(c =>
{
c.AddCommandLine(new string[] { $"ContentRoot={FileSystem.AppDataDirectory}" });
})
.ConfigureServices((c, x) =>
{
ConfigureServices(c, x);
})
.Build();
ServiceProvider = host.Services;
}
static void ConfigureServices(HostBuilderContext ctx, IServiceCollection services)
{
services.AddSingleton<Helpers.ClientService>();
services.AddHttpClient("webClient", client =>
{
client.DefaultRequestHeaders.Authorization = GenerateAuthHeader();
})
.AddPolicyHandler(GetRetryPolicy());
}
Then getting the client for use like
public class ClientService
{
IHttpClientFactory _httpClientFactory;
public ClientService(IHttpClientFactory factory)
{
_httpClientFactory = factory;
}
public HttpClient GetClient()
{
return _httpClientFactory.CreateClient(App.ClientName);
}
}
Helpers.ClientService service = App.ServiceProvider.GetService<Helpers.ClientService>();
HttpClient client = service.GetClient();
HttpResponseMessage response = await client.GetAsync(url);
On app start and resume I am re-running Init method.
Is this re-initializing the factory correctly, if not how should I implement this functionality
I am trying integration tests with XUnit. I can access the endpoints that have been assigned on MapGet but to not actions inside controllers. I get a NotFound error. I am running the application on an API project with ApplicationFactory : WebApplicationFactory but all requests that I make with Factory.Client return NotFound. Is there any definition needed in integration tests in order to be able to send requests to controllers?
Test projects code:
private ApplicationFactory Factory { get; }
public AccountsControllerTests(ITestOutputHelper output)
{
Factory = new ApplicationFactory(output);
}
[Fact]
public async Task ListBalancesTest()
{
var client = Factory.CreateClient(new WebApplicationFactoryClientOptions(){AllowAutoRedirect = false});;
var resp = await client.GetAsync($"api/v1/test/get");
//!!!!!!! resp.StatusCode is HttpStatusCode.NotFound !!!!!!!
var mapgetResp= await client.GetAsync($"/test");
//!!!!!!! mapgetResp is Working !!!!!!!
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
API Code:
[ApiController]
[Route("api/v1/test")]
public class TestController : ControllerBase
{
[HttpGet("get")]
public async Task<ActionResult> Get()
{
return await Task.FromResult(Ok("Response from test"));
}
}
ApplicationFactory Code:
public class ApplicationFactory : WebApplicationFactory<TestStartup>
{
public ApplicationFactory(ITestOutputHelper testOutput = null)
{
_testOutput = testOutput;
}
protected override IWebHostBuilder CreateWebHostBuilder()
{
var builder = WebHost
.CreateDefaultBuilder()
.UseEnvironment("Development")
.UseStartup<TestStartup>()
.UseSerilog();
if (_testOutput != null)
{
builder = builder.ConfigureLogging(logging =>
{
logging.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<ILoggerProvider>(new TestOutputHelperLoggerProvider(_testOutput)));
});
}
return builder;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseContentRoot(".");
builder.ConfigureServices(services =>
{/...../}
}
}
Startup:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapGet("/test", async context =>
{
await context.Response.WriteAsync(#$"Test value from MapGet");
});
/..../
});
Add code in API project in ConfigureWebHost(IWebHostBuilder builder)
builder.UseEnvironment("Test");
builder.ConfigureAppConfiguration((ctx, b) =>
{
ctx.HostingEnvironment.ApplicationName = typeof(Program).Assembly.GetName().Name;
});
builder.ConfigureServices(services =>
{
services.AddMvc()
.AddApplicationPart(typeof(TestStartup).Assembly); //TestStartup is Test project's startup!!
}
And works now thanks!
I have a working web API that I recently updated to use JWT auth and, while it is working when I run it normally, I can't seem to get my integration tests to work.
I wanted to start working on integrating a token generation option for my integration tests, but I can't even get them to throw a 401.
When I run any of my preexisting integration tests that work without JWT in the project, I'd expect to get a 401 since I don't have any auth info, but am actually getting a System.InvalidOperationException : Scheme already exists: Bearer error.
I'd assume this is happening because of the way that WebApplicationFactory works by running its ConfigureWebHost method runs after the Startup class' ConfigureServices method and when i put a breakpoint on my jwt service, it does indeed get hit twice, but given that this is how WebApplicationFactory is built, I'm not sure what the recommended option here is. Of note, even when I remove one of the services I still get the error:
var serviceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(JwtBearerHandler));
services.Remove(serviceDescriptor);
My WebApplicationFactory is based on the eshopwebapi factory:
public class CustomWebApplicationFactory : WebApplicationFactory<StartupTesting>
{
// checkpoint for respawning to clear the database when spinning up each time
private static Checkpoint checkpoint = new Checkpoint
{
};
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Testing");
builder.ConfigureServices(async services =>
{
// Create a new service provider.
var provider = services
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Add a database context (LabDbContext) using an in-memory
// database for testing.
services.AddDbContext<LabDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
options.UseInternalServiceProvider(provider);
});
// Build the service provider.
var sp = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database
// context (ApplicationDbContext).
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<LabDbContext>();
// Ensure the database is created.
db.Database.EnsureCreated();
try
{
await checkpoint.Reset(db.Database.GetDbConnection());
}
catch
{
}
}
}).UseStartup<StartupTesting>();
}
public HttpClient GetAnonymousClient()
{
return CreateClient();
}
}
This is my service registration:
public static class ServiceRegistration
{
public static void AddIdentityInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = configuration["JwtSettings:Authority"];
options.Audience = configuration["JwtSettings:Audience"];
});
services.AddAuthorization(options =>
{
options.AddPolicy("CanReadPatients",
policy => policy.RequireClaim("scope", "patients.read"));
options.AddPolicy("CanAddPatients",
policy => policy.RequireClaim("scope", "patients.add"));
options.AddPolicy("CanDeletePatients",
policy => policy.RequireClaim("scope", "patients.delete"));
options.AddPolicy("CanUpdatePatients",
policy => policy.RequireClaim("scope", "patients.update"));
});
}
}
And this is my integration test (that I would expect to currently throw a 401):
public class GetPatientIntegrationTests : IClassFixture<CustomWebApplicationFactory>
{
private readonly CustomWebApplicationFactory _factory;
public GetPatientIntegrationTests(CustomWebApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task GetPatients_ReturnsSuccessCodeAndResourceWithAccurateFields()
{
var fakePatientOne = new FakePatient { }.Generate();
var fakePatientTwo = new FakePatient { }.Generate();
var appFactory = _factory;
using (var scope = appFactory.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<LabDbContext>();
context.Database.EnsureCreated();
context.Patients.AddRange(fakePatientOne, fakePatientTwo);
context.SaveChanges();
}
var client = appFactory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
var result = await client.GetAsync("api/Patients")
.ConfigureAwait(false);
var responseContent = await result.Content.ReadAsStringAsync()
.ConfigureAwait(false);
var response = JsonConvert.DeserializeObject<Response<IEnumerable<PatientDto>>>(responseContent).Data;
// Assert
result.StatusCode.Should().Be(200);
response.Should().ContainEquivalentOf(fakePatientOne, options =>
options.ExcludingMissingMembers());
response.Should().ContainEquivalentOf(fakePatientTwo, options =>
options.ExcludingMissingMembers());
}
}
Hey I saw your post when I was looking for the same answer. I solved it by putting the following code in the ConfigureWebHost method of my WebApplicationFactory:
protected override void ConfigureWebHost(
IWebHostBuilder builder)
{
builder.ConfigureServices(serviceCollection =>
{
});
// Overwrite registrations from Startup.cs
builder.ConfigureTestServices(serviceCollection =>
{
var authenticationBuilder = serviceCollection.AddAuthentication();
authenticationBuilder.Services.Configure<AuthenticationOptions>(o =>
{
o.SchemeMap.Clear();
((IList<AuthenticationSchemeBuilder>) o.Schemes).Clear();
});
});
}
I know I'm four months late, but I hope you still have any use for it.
I am attempting to run a background worker for a web app that I am developing. I am using Npgsql as my EF Core provider.
For clarification, I have injected my DbContext with a Transient lifetime, and have allowed Pooling in my connection string, however, whenever I try to test it I get the following error:
Npgsql.NpgsqlOperationInProgressException: A command is already in progress: [My Query Here]
I have my Program Class Set up as such:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
// Get the configuration
IConfiguration config = hostContext.Configuration;
// DbContext
services.AddDbContext<MyDbContext>(options => options.UseNpgsql(config.GetConnectionString("PostgreSQLString")), ServiceLifetime.Transient);
services.AddHostedService<Worker>();
services.AddScoped<IDTOService, BackgroundDTOService>();
});
}
Which then leads to my Worker class
public class Worker : BackgroundService
{
private Logger logger;
public Worker(IServiceProvider services, IConfiguration configuration)
{
this.Services = services;
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgreSQLString"));
var context = new StatPeekContext(optionsBuilder.Options);
this.logger = new Logger(new LogWriter(context));
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
this.logger.LogInformation("ExecuteAsync in Worker Service is running.");
await this.DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
using (var scope = Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
var dtoService = scope.ServiceProvider.GetRequiredService<IDTOService>();
await dtoService.ProcessJSON(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
this.logger.LogInformation("Worker service is stopping.");
await Task.CompletedTask;
}
}
which leads to my BackGroundDTOService
public class BackgroundDTOService : IDTOService
{
private int executionCount = 0;
private Logger logger;
private MyDbContext context;
private DbContextOptionsBuilder<MyDbContext> optionsBuilder;
public BackgroundDTOService(IConfiguration configuration, MyDbContext context)
{
this.optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
this.optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgreSQLString"));
this.logger = new Logger(new LogWriter(new MyDbContext(this.optionsBuilder.Options)));
this.context = context;
}
public async Task ProcessJSON(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
this.executionCount++;
this.logger.LogInformation($"DTO Service is working. Count: {this.executionCount}");
this.ProcessTeams();
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
}
}
public void ProcessTeams()
{
// Add Any Franchises that don't exist
var franchiseDumps = this.context.RequestDumps.Where(rd => rd.Processed == false && rd.DumpType == "leagueteams");
foreach (RequestDump teamDump in franchiseDumps)
{
var league = this.context.Leagues.Include(l => l.Franchises).FirstOrDefault(l => l.Id == teamDump.LeagueID);
var teams = Jeeves.GetJSONFromKey<List<DTOTeam>>(teamDump.JsonDump, "leagueTeamInfoList");
foreach (DTOTeam team in teams)
{
this.UpdateFranchise(team, league);
}
this.logger.LogInformation($"DTO Service Processed League Teams on count {this.executionCount}");
}
this.context.SaveChanges();
}
The error appears to occur immediately after snagging franchiseDumps when it tries to get league
Could you try materialising the query:
var franchiseDumps = this.context.RequestDumps.Where(rd => rd.Processed == false && rd.DumpType == "leagueteams").ToList();
I had the same issue and I realized that PostgreSQL doesn't support Multiple Active Result Sets (MARS) compare to MSSQL.
An alternative way that worked for me to fetch objects with foreign key:
IEnumerable<Expense> objList = _db.Expenses;
foreach (var obj in objList.ToList())
{
obj.ExpenseCategory = _db.ExpenseCategories.FirstOrDefault(u => u.Id == obj.ExpenseCategoryId);
}
I am following the official MS documentation for integration testing .Net Core (https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.1).
I was able to get the first part of the integration test done where I was not overriding the startup class of the application I am testing (i.e. I was using a web application factorythat did not override any services).
I want to override the database setup to use an in-memory database for the integration test. The problem I am running into is that the configuration continues to try and use the sql server for services.AddHangfire().
How do I override only above specific item in my integration test? I only want to override the AddHangfire setup and not services.AddScoped<ISendEmail, SendEmail>(). Any help would be appreciated.
Test Class with the custom web application factory
public class HomeControllerShouldCustomFactory : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<Startup> _factory;
public HomeControllerShouldCustomFactory(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = factory.CreateClient();
}
[Fact]
public async Task IndexRendersCorrectTitle()
{
var response = await _client.GetAsync("/Home/Index");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
Assert.Contains("Send Email", responseString);
}
}
Custom Web Application Factory
public class CustomWebApplicationFactory<TStartup>: WebApplicationFactory<SendGridExample.Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Create a new service provider.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
var inMemory = GlobalConfiguration.Configuration.UseMemoryStorage();
services.AddHangfire(x => x.UseStorage(inMemory));
// Build the service provider.
var sp = services.BuildServiceProvider();
});
}
}
My startup.cs in my application that I am testing
public IConfiguration Configuration { get; }
public IHostingEnvironment Environment { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("ASP_NetPractice")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddScoped<ISendEmail, SendEmail>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseHangfireServer();
app.UseHangfireDashboard();
RecurringJob.AddOrUpdate<ISendEmail>((email) => email.SendReminder(), Cron.Daily);
app.UseMvc();
Update
I don't see this issue in my other example project where I am using only entity framework. I have a simple application with an application db context which uses SQL server. In my test class, I override it with an in-memory database and everything works. I am at a loss at to why it will work in my example application but not work in my main application. Is this something to do with how HangFire works?
In my test application (example code below), I can delete my sql database, run my test, and the test passes because the application DB context does not go looking for the sql server instance but uses the in-memory database. In my application, the HangFire service keeps trying to use the sql server database (if I delete the database and try to use an in-memory database for the test - it fails because it can't find the instance its trying to connect to). How come there is such a drastic difference in how the two projects work when a similar path is used for both?
I ran through the debugger for my integration test which calls the index method on the home controller above (using the CustomWebApplicationFactory). As I am initializing a test server, it goes through my startup class which calls below in ConfigureServices:
services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("ASP_NetPractice")));
After that, the Configure method tries to call below statement:
app.UseHangfireServer();
At this point the test fails as It cannot find the DB. The DB is hosted on Azure so I am trying to replace it with an in-memory server for some of the integration test. Is the approach I am taking incorrect?
My example application where its working
Application DB Context in my example application
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public virtual DbSet<Message> Messages { get; set; }
public async Task<List<Message>> GetMessagesAsync()
{
return await Messages
.OrderBy(message => message.Text)
.AsNoTracking()
.ToListAsync();
}
public void Initialize()
{
Messages.AddRange(GetSeedingMessages());
SaveChanges();
}
public static List<Message> GetSeedingMessages()
{
return new List<Message>()
{
new Message(){ Text = "You're standing on my scarf." },
new Message(){ Text = "Would you like a jelly baby?" },
new Message(){ Text = "To the rational mind, nothing is inexplicable; only unexplained." }
};
}
}
Startup.cs in my example application
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
CustomWebApplicationFactory - in my unit test project
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Create a new service provider.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Add a database context (ApplicationDbContext) using an in-memory
// database for testing.
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
options.UseInternalServiceProvider(serviceProvider);
});
// Build the service provider.
var sp = services.BuildServiceProvider();
});
}
}
My unit test in my unit test project
public class UnitTest1 : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<Startup> _factory;
public UnitTest1(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = factory.CreateClient();
}
[Fact]
public async System.Threading.Tasks.Task Test1Async()
{
var response = await _client.GetAsync("/");
//response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
Assert.Contains("Home", responseString);
}
Update 2
I think I found an alternate to trying to override all my configuration in my integration test class. Since it's a lot more complicated to override HangFire as opposed to an ApplicationDBContext, I came up with below approach:
Startup.cs
if (Environment.IsDevelopment())
{
var inMemory = GlobalConfiguration.Configuration.UseMemoryStorage();
services.AddHangfire(x => x.UseStorage(inMemory));
}
else
{
services.AddHangfire(x => x.UseSqlServerStorage(Configuration["DBConnection"]));
}
Then in my CustomWebApplicationBuilder, I override the environment type for testing:
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<SendGridExample.Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Development"); //change to Production for alternate test
builder.ConfigureServices(services =>
{
// Create a new service provider.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
});
}
}
With that approach, I don't need to worry about having to do extra logic to satisfy hangfire's check for an active DB. It works but I am not 100% convinced its the best approach as I'm introducing branching in my production startup class.
There are two different scenarios you need to check.
Create a job by class BackgroundJob
Create a job by interface IBackgroundJobClient
For the first option, you could not replace the SqlServerStorage with MemoryStorage.
For UseSqlServerStorage, it will reset JobStorage by SqlServerStorage.
public static IGlobalConfiguration<SqlServerStorage> UseSqlServerStorage(
[NotNull] this IGlobalConfiguration configuration,
[NotNull] string nameOrConnectionString)
{
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
if (nameOrConnectionString == null) throw new ArgumentNullException(nameof(nameOrConnectionString));
var storage = new SqlServerStorage(nameOrConnectionString);
return configuration.UseStorage(storage);
}
UseStorage
public static class GlobalConfigurationExtensions
{
public static IGlobalConfiguration<TStorage> UseStorage<TStorage>(
[NotNull] this IGlobalConfiguration configuration,
[NotNull] TStorage storage)
where TStorage : JobStorage
{
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
if (storage == null) throw new ArgumentNullException(nameof(storage));
return configuration.Use(storage, x => JobStorage.Current = x);
}
Which means, no matter what you set in CustomWebApplicationFactory, UseSqlServerStorage will reset BackgroundJob with SqlServerStorage.
For second option, it could replace IBackgroundJobClient with MemoryStorage by
public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services.AddSingleton<JobStorage>(x =>
{
return GlobalConfiguration.Configuration.UseMemoryStorage();
});
});
}
}
In conclusion, I suggest you register IBackgroundJobClient and try the second option to achieve your requirement.
Update1
For DB is not available, it could not be resolved by configuring the Dependency Injection. This error is caused by calling services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("ASP_NetPractice")));.
For resolving this error, you need to overriding this code in Startup.cs.
Try steps below:
Change Startup to below:
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)
{
//Rest Code
ConfigureHangfire(services);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//Rest Code
app.UseHangfireServer();
RecurringJob.AddOrUpdate(() => Console.WriteLine("RecurringJob!"), Cron.Minutely);
}
protected virtual void ConfigureHangfire(IServiceCollection services)
{
services.AddHangfire(config =>
config.UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection"))
);
}
}
Create StartupTest in test project.
public class StartupTest : Startup
{
public StartupTest(IConfiguration configuration) :base(configuration)
{
}
protected override void ConfigureHangfire(IServiceCollection services)
{
services.AddHangfire(x => x.UseMemoryStorage());
}
}
CustomWebApplicationFactory
public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint> where TEntryPoint: class
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost.CreateDefaultBuilder(null)
.UseStartup<TEntryPoint>();
}
}
Test
public class HangfireStorageStartupTest : IClassFixture<CustomWebApplicationFactory<StartupTest>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<StartupTest> _factory;
public HangfireStorageStartupTest(CustomWebApplicationFactory<StartupTest> factory)
{
_factory = factory;
_client = factory.CreateClient();
}
}