DI issue in .NET Core - c#

I have an odata query builder class that I am using to build my odata string that is desterilising the result based on the object that called it.
public class UosOdataQueryBuilder<T>
{
private readonly Dictionary<string, string> _queryOptions;
private readonly IHttpClientFactory _clientFactory;
private readonly ILogger _logger;
public UosOdataQueryBuilder([FromServices] IHttpClientFactory clientFactory, [FromServices] ILogger logger)
{
_queryOptions = new Dictionary<string, string>();
_clientFactory = clientFactory;
_logger = logger;
}
public UosOdataQueryBuilder<T> WithFilter(string filter)
{
_queryOptions.Add("$filter", filter);
return this;
}
public UosOdataQueryBuilder<T> Skip(int skip)
{
_queryOptions.Add("$skip", skip.ToString());
return this;
}
public UosOdataQueryBuilder<T> Top(int top)
{
_queryOptions.Add("$top", top.ToString());
return this;
}
public UosOdataQueryBuilder<T> WithNoInlineCount()
{
_queryOptions.Add("$inlinecount", "none");
return this;
}
public UosOdataQueryBuilder<T> OrderBy(string orderBy)
{
_queryOptions.Add("$orderby", orderBy);
return this;
}
public async Task<UosOdataReponse<T>> ExecuteQueryAsync(string elementName = "")
{
var result = new UosOdataReponse<T>();
try
{
var authToken = AppSettings.PlatformBearerToken;
var queryParameters = new List<string>();
foreach (var option in _queryOptions)
queryParameters.Add($"{option.Key}={option.Value}");
var queryParametersCombined = string.Join("&", queryParameters);
var oDataElementName = (elementName == "") ? typeof(T).Name : elementName;
var baseUrl = AppSettings.PlatformBaseUri;
var client = _clientFactory.CreateClient("UOS");
var request = new HttpRequestMessage(
HttpMethod.Get,
new Uri(baseUrl + $"/uos/v4/odata/{oDataElementName}" + queryParametersCombined));
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<UosOdataReponse<T>>(data);
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
return result;
}
}
I have setup the client in startup
services.AddHttpClient("UOS", c =>
{
c.BaseAddress = new Uri(Configuration.GetValue<string>("PlatformBaseUri") + "uos/v4/");
c.DefaultRequestHeaders.Add("Accept", "application/json");
c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Configuration.GetValue<string>("PlatformBearerToken"));
//c.Timeout = new TimeSpan(0, 0, 30);
});
When I create a new instance of this from another method it is requiring that I pass in the clientFactory and logger.
protected async Task<int> GetUosOdataCount(string filter)
{
var result = new List<T>();
try
{
var countCheck = await new UosOdataQueryBuilder<T>()
.WithFilter(filter)
.Top(1)
.ExecuteQueryAsync();
return countCheck.Count;
}
catch (Exception ex)
{
//CustomLogger.LogError(GetType().FullName, "GetUosOdata", ex.Message);
}
}
In .NET Framework I would remove the parameters from the constructor of the UosOdataQueryBuilder and resolve the dependencies within it. For Example:
_uosUserAttributeRepository = GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IUosUserAttributeRepository)) as IUosUserAttributeRepository;
But I am not sure how to achieve in .NET Core. Any suggestions?

You can create an interface for UosOdataQueryBuilder<T> and register it into DI generically. some thing like this:
public interface IUosOdataQueryBuilder<T>
{
Task<T> SomeMethod();
}
public class UosOdataQueryBuilder<T> : IUosOdataQueryBuilder<T>
{
private readonly Dictionary<string, string> _queryOptions;
private readonly IHttpClientFactory _clientFactory;
private readonly ILogger<UosOdataQueryBuilder<T>> _logger;
public UosOdataQueryBuilder(IHttpClientFactory clientFactory, ILogger<UosOdataQueryBuilder<T>> logger)
{
_queryOptions = new Dictionary<string, string>();
_clientFactory = clientFactory;
_logger = logger;
}
public Task<T> SomeMethod()
{
return default;
}
}
And in ConfigureServices in startup write this:
services.AddScoped(typeof(IUosOdataQueryBuilder<>), typeof(UosOdataQueryBuilder<>));
And in your controller inject the IUosOdataQueryBuilder:
private readonly IUosOdataQueryBuilder<YourClass> _uosOdataQueryBuilder;
public YourController( IUosOdataQueryBuilder<YourClass> uosOdataQueryBuilder)
{
_uosOdataQueryBuilder = uosOdataQueryBuilder;
}
you can register IUosOdataQueryBuilder as Singleton but for prevent memory leak you should inject IServiceScopeFactory in concrete class to get registered service in your methods not in constructor.

I'd agree with comment to stick with injection but you can retrieve the dependencies within the class using IServiceProvider
e.g
MyMethod(IServiceProvider serviceProvider)
{
MyService svc = (MyService)serviceProvider.GetService(typeof(MyService));
...

Related

How to use LoggingBehaviour in Clean Architecture with .NET Core?

I use CleanArchitecture for my .NET Core project with Angular and I am trying to use LoggingBehaviour class located on CleanArchitecture/src/Application/Common/Behaviours/ in that project template as shown below:
namespace CleanArchitecture.Application.Common.Behaviours
{
public class LoggingBehaviour<TRequest> : IRequestPreProcessor<TRequest>
{
private readonly ILogger _logger;
private readonly ICurrentUserService _currentUserService;
private readonly IIdentityService _identityService;
public LoggingBehaviour(ILogger<TRequest> logger, ICurrentUserService currentUserService,
IIdentityService identityService)
{
_logger = logger;
_currentUserService = currentUserService;
_identityService = identityService;
}
public async Task Process(TRequest request, CancellationToken cancellationToken)
{
var requestName = typeof(TRequest).Name;
var userId = _currentUserService.UserId ?? string.Empty;
string userName = string.Empty;
if (!string.IsNullOrEmpty(userId))
{
userName = await _identityService.GetUserNameAsync(userId);
}
_logger.LogInformation("CleanArchitecture Request: {Name} {#UserId} {#UserName} {#Request}",
requestName, userId, userName, request);
}
}
}
However, I have no idea about how to use it properly as there is not an example usage in that solution template. Could you please clarify me on how to use it properly according to this template example?
You can use like below;
namespace CleanArchitecture.Application.UnitTests.Common.Behaviours
{
public class RequestLoggerTests
{
private readonly Mock<ILogger<CreateTodoItemCommand>> _logger;
private readonly Mock<ICurrentUserService> _currentUserService;
private readonly Mock<IIdentityService> _identityService;
public RequestLoggerTests()
{
_logger = new Mock<ILogger<CreateTodoItemCommand>>();
_currentUserService = new Mock<ICurrentUserService>();
_identityService = new Mock<IIdentityService>();
}
[Test]
public async Task ShouldCallGetUserNameAsyncOnceIfAuthenticated()
{
_currentUserService.Setup(x => x.UserId).Returns("Administrator");
var requestLogger = new LoggingBehaviour<CreateTodoItemCommand>(_logger.Object, _currentUserService.Object, _identityService.Object);
await requestLogger.Process(new CreateTodoItemCommand { ListId = 1, Title = "title" }, new CancellationToken());
_identityService.Verify(i => i.GetUserNameAsync(It.IsAny<string>()), Times.Once);
}
[Test]
public async Task ShouldNotCallGetUserNameAsyncOnceIfUnauthenticated()
{
var requestLogger = new LoggingBehaviour<CreateTodoItemCommand>(_logger.Object, _currentUserService.Object, _identityService.Object);
await requestLogger.Process(new CreateTodoItemCommand { ListId = 1, Title = "title" }, new CancellationToken());
_identityService.Verify(i => i.GetUserNameAsync(null), Times.Never);
}
}
}

Polly FallbackAsync policy with a dynamic action that raises an event so that the request / response can be written to a database

I'm looking for a way to attach a generic Polly fallback policy to my typed HttpClient. My policy gets applied to certain request types as and when required, otherwise it applies a NoOpPolicy. Im interested in the onFallbackAsync Task. I need to save the request/response to a database table, but I'm struggling to figure out how to apply this because I can't see how to inject a dependency. I seem to be missing something obvious, but I don't know what. Can this be done with a delegating handler?
My typed client basically looks like this, and is used for a variety of different API calls:
public class ApiClient : IApiClient
{
private readonly HttpClient _httpClient;
private readonly ILogger _logger;
private HttpRequestMessage _httpRequestMessage;
private HttpContent _httpContent;
private IDictionary<string, string> _queryParameters;
public ApiClient(HttpClient httpClient, ILogger logger)
{
_httpClient = httpClient;
_logger = logger;
_httpRequestMessage = new HttpRequestMessage();
_queryParameters = new Dictionary<string, string>();
}
public void SetUrl(string urlPath)
{
urlPath = urlPath.TrimEnd('/');
_httpClient.BaseAddress = new Uri(urlPath);
}
public void SetQueryParamters(IDictionary<string, string> parameters)
{
_queryParameters = parameters;
}
public void AddQueryParamter(string key, string value)
{
_queryParameters.Add(key, value);
}
public async Task<T> Get<T>()
{
_httpRequestMessage.Method = HttpMethod.Get;
return await EnvokeService<T>();
}
public async Task<T> PostJsonAsync<T>(object body)
{
var settings = new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore
};
_httpContent = new JsonContent(JsonConvert.SerializeObject(body, settings));
_httpRequestMessage.Method = HttpMethod.Post;
return await EnvokeService<T>();
}
private async Task<T> EnvokeService<T>()
{
string responseContent = null;
try
{
_httpRequestMessage.Content = _httpContent;
_httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
var responseMessage = await _httpClient.SendAsync(_httpRequestMessage, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
responseContent = await responseMessage.Content.ReadAsStringAsync();
if (responseMessage.IsSuccessStatusCode)
{
T result;
var serializer = new JsonSerializer();
using (var sr = new StreamReader(await responseMessage.Content.ReadAsStreamAsync()))
using (var jsonTextReader = new JsonTextReader(sr))
{
result = serializer.Deserialize<T>(jsonTextReader);
}
_logger.Debug(logMessage);
return result;
}
throw new HttpRequestException($"Error Code: {responseMessage.StatusCode}. Response: {responseContent}");
}
catch (Exception e)
{
_logger.Exception(e, "Error");
throw;
}
}
}
It is setup like this:
public static class ApiHttpClientConfigurator
{
public static void AddApiHttpClient (this IServiceCollection services)
{
IPolicyRegistry<string> registry = services.AddPolicyRegistry();
registry.Add("WaitAndRetryPolicy", ResiliencePolicies.GetHttpPolicy(new TimeSpan(0, 1, 0)));
registry.Add("NoOpPolicy", Policy.NoOpAsync().AsAsyncPolicy<HttpResponseMessage>());
services.AddHttpClient<ApiHttpClient>().AddPolicyHandlerFromRegistry(PolicySelector);
services.AddSingleton<IApiHttpClientFactory, ApiHttpClientFactory>();
}
private static IAsyncPolicy<HttpResponseMessage> PolicySelector(IReadOnlyPolicyRegistry<string> policyRegistry, HttpRequestMessage httpRequestMessage)
{
// if we have a message of type X then apply the policy
if (httpRequestMessage.RequestUri.AbsoluteUri.Contains("/definitely/apply/a/retry/policy"))
{
return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("WaitAndRetryPolicy");
}
return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("NoOpPolicy");
}
}
Registered as so:
services.AddApiHttpClient();
My policies are defined as such:
public static class ResiliencePolicies
{
public static IAsyncPolicy<HttpResponseMessage> HttpFallBackPolicy
{
get => Policy<HttpResponseMessage>.Handle<Exception>().FallbackAsync(new HttpResponseMessage(HttpStatusCode.InternalServerError), d =>
{
// TODO: publish event
InstanceOfMediatr.Publish(new RequestFailedEvent(request/response/data/here))
Log.Warning($"Fallback: {d.Exception.GetType().Name} {d.Exception.Message}");
return Task.CompletedTask;
});
}
public static IAsyncPolicy<HttpResponseMessage> HttpRetryPolicy
{
get => HttpPolicyExtensions.HandleTransientHttpError()
.Or<TimeoutRejectedException>()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt * 0.5));
}
public static IAsyncPolicy<HttpResponseMessage> GetHttpTimeoutPolicy(TimeSpan timeSpan) =>
Policy.TimeoutAsync<HttpResponseMessage>(timeSpan);
public static IAsyncPolicy<HttpResponseMessage> GetHttpPolicy(TimeSpan timeout) =>
Policy.WrapAsync(HttpFallBackPolicy, HttpRetryPolicy, GetHttpTimeoutPolicy(timeout));
}
The final part of the puzzle would appear to be, how can I complete the TODO section above? I seem to need a dynamic fallback action, but I'm really not sure how to implement it, or whether or not it is even possible.
Just in case anyone else comes across this problem, there are two ways to solve it depending upon your requirements (using AddPolicyHandlerFromRegistry() or AddPolicyHandler()).
Through IServiceProvider
AddPolicyHandler() has a convenient overload where you can inject IServiceProvider:
.AddPolicyHandler((provider, message) =>
HttpPolicyExtensions
.HandleTransientHttpError()
.FallbackAsync(
fallbackValue: new HttpResponseMessage(HttpStatusCode.InternalServerError),
onFallbackAsync: (result, context) =>
{
var publisher = provider.GetService<IPublisher>();
publisher.Publish(new HttpRequestFallbackEvent());
return Task.CompletedTask;
}))
Through Context
If you are using a PolicyRegistry and AddPolicyHandlerFromRegistry(), then it is easier to use the Context as described here. First extend the Polly's Context:
public static class ContextExtensions
{
private static readonly string PublisherKey = "PublisherKey";
public static Context WithDependencies(this Context context, IPublisher publisher)
{
context[PublisherKey] = publisher;
return context;
}
public static IPublisher GetPublisher(this Context context)
{
if (context.TryGetValue(PublisherKey, out object publisher))
{
return publisher as IPublisher;
}
return null;
}
}
Then in your client, inject your dependency (i.e. IPublisher) and add it to the new Context and add that to the executing context:
var context = new Context().WithDependencies(_publisher);
request.SetPolicyExecutionContext(context);
var responseMessage = await _httpClient.SendAsync(request)
Now you can use that in your registered and selected policy:
private static IAsyncPolicy<HttpResponseMessage> FallbackPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.Or<Exception>()
.FallbackAsync(
fallbackValue: new HttpResponseMessage(HttpStatusCode.InternalServerError),
onFallbackAsync: (result, context) =>
{
var publisher = context.GetPublisher();
publisher.Publish(new HttpRequestFallbackEvent());
return Task.CompletedTask;
}));
}

Dependency Injection with XUnit Mediatr and IServiceCollection

Currently I'm able to handle IServiceCollection to inject mocks for particular services in the following manner.
public class TestClass
{
private IMediator _mediatr;
private void SetupProvider(IUnitOfWork unitOfWork, ILogger logger)
{
configuration = new ConfigurationBuilder().Build();
_services = new ServiceCollection();
_services.AddSingleton(configuration);
_services.AddScoped(x => unitOfWork);
_services.AddSingleton(logger);
_services.AddMediatR(Assembly.Load("Application"));
_services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggerBehaviour<,>));
_mediator = _services.BuildServiceProvider().GetService<IMediator>();
}
[Fact]
public async void UnitTest_Success()
{
var unitOfWork = new Mock<IUnitOfWork>();
var logger = new Mock<ILogger>();
SetupProvider(unitOfWork.Object, logger.Object);
var fixture = new Fixture();
var command = fixture.Create<MediatorCommand>();
unitOfWork.Setup(x => x.Repository.FindAll(It.IsAny<IList<long>>(), It.IsAny<bool?>()))
.ReturnsAsync(new List<Domain.Model>());
var response = await _mediatr.Send(command);
using (new AssertionScope())
{
response.Should().NotBeNull();
response.IsSuccess.Should().BeTrue();
}
}
}
For the following subject under test
public class MediatorCommand : IRequest<CommandResponse>
{
public string Name { get; set ;}
public string Address { get; set; }
}
public class MediatorCommandHandler : IRequestHandler<MediatorCommand, CommandResponse>
{
private readonly ILogger _logger;
private readonly IUnitOfWork _unitOfWork;
public MediatorCommandHandler(IUnitOfWork unitOfWork, ILogger logger)
{
_logger = logger;
_unitOfWork = unitOfWork;
}
public async Task<CommandResponse> Handle(MediatorCommand command, CancellationToken cancellationToken)
{
var result = new CommandResponse { IsSuccess = false };
try
{
var entity = GetEntityFromCommand(command);
await _unitOfWork.Save(entity);
result.IsSuccess = true;
}
catch(Exception ex)
{
_logger.LogError(ex, ex.Message);
}
return result;
}
}
This test runs fine and the unitOfWork and logger mocks are used in the command handlers.
I'm try to move this so that the IServiceCollection construction happens per class instead of each test using the following:
public class SetupFixture : IDisposable
{
public IServiceCollection _services;
public IMediator Mediator { get; private set; }
public Mock<IUnitOfWork> UnitOfWork { get; private set; }
public SetupFixtureBase()
{
UnitOfWork = new Mock<IUnitOfWork>();
configuration = new ConfigurationBuilder().Build();
_services = new ServiceCollection();
_services.AddSingleton(configuration);
_services.AddScoped(x => UnitOfWork);
_services.AddSingleton(new Mock<ILogger>().Object);
_services.AddMediatR(Assembly.Load("Application"));
_services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggerBehaviour<,>));
Mediator = _services.BuildServiceProvider().GetService<IMediator>();
}
public void Dispose()
{
Mediator = null;
_services.Clear();
_services = null;
}
}
public class TestClass : IClassFixture<SetupFixture>
{
protected readonly SetupFixture _setupFixture;
public UnitTestBase(SetupFixture setupFixture)
{
_setupFixture = setupFixture;
}
[Fact]
public async void UnitTest_Success()
{
var fixture = new Fixture();
var command = fixture.Create<MediatorCommand>();
_setupFixture.UnitOfWork.Setup(x => x.Repository.FindAll(It.IsAny<IList<long>>(), It.IsAny<bool?>()))
.ReturnsAsync(new List<Domain.Model>());
var response = await _mediatr.Send(command);
using (new AssertionScope())
{
response.Should().NotBeNull();
response.IsSuccess.Should().BeTrue();
}
}
}
Unfortunately with this method my mocks do not get injected on the command handler. Is there a way to get this to work?
Thank you,
I found the issue and it is not related to moving to IClassFixuture<>. The issue was that I was initializing Mediator on a base class an then adding the mock UnitOfWork on a derived class.
This cause the Mediator initialization to fail because one of the beheviours expected the UnitOfWork which at the time was not yet on the container.
Moving the initialization of Mediator after all the services have been added helped me resolve the issue and now all works as expected.
If you try the same thing, please make sure to include all the services in the container before initializing any objects that require those dependencies.
Thank you all those who had input.

Property injection

I'm trying make a telegram bot with reminder. I'm using Telegram.Bot 14.10.0, Quartz 3.0.7, .net core 2.0. The first version should : get message "reminder" from telegram, create job (using Quartz) and send meaasage back in 5 seconds.
My console app with DI looks like:
Program.cs
static IBot _botClient;
public static void Main(string[] args)
{
// it doesn't matter
var servicesProvider = BuildDi(connecionString, section);
_botClient = servicesProvider.GetRequiredService<IBot>();
_botClient.Start(appModel.BotConfiguration.BotToken, httpProxy);
var reminderJob = servicesProvider.GetRequiredService<IReminderJob>();
reminderJob.Bot = _botClient;
Console.ReadLine();
_botClient.Stop();
// it doesn't matter
}
private static ServiceProvider BuildDi(string connectionString, IConfigurationSection section)
{
var rJob = new ReminderJob();
var sCollection = new ServiceCollection()
.AddSingleton<IBot, Bot>()
.AddSingleton<ReminderJob>(rJob)
.AddSingleton<ISchedulerBot>(s =>
{
var schedBor = new SchedulerBot();
schedBor.StartScheduler();
return schedBor;
});
return sCollection.BuildServiceProvider();
}
Bot.cs
public class Bot : IBot
{
static TelegramBotClient _botClient;
public void Start(string botToken, WebProxy httpProxy)
{
_botClient = new TelegramBotClient(botToken, httpProxy);
_botClient.OnReceiveError += BotOnReceiveError;
_botClient.OnMessage += Bot_OnMessage;
_botClient.StartReceiving();
}
private static async void Bot_OnMessage(object sender, MessageEventArgs e)
{
var me = wait _botClient.GetMeAsync();
if (e.Message.Text == "reminder")
{
var map= new Dictionary<string, object> { { ReminderJobConst.ChatId, e.Message.Chat.Id.ToString() }, { ReminderJobConst.HomeWordId, 1} };
var job = JobBuilder.Create<ReminderJob>().WithIdentity($"{prefix}{rnd.Next()}").UsingJobData(new JobDataMap(map)).Build();
var trigger = TriggerBuilder.Create().WithIdentity($"{prefix}{rnd.Next()}").StartAt(DateTime.Now.AddSeconds(5).ToUniversalTime())
.Build();
await bot.Scheduler.ScheduleJob(job, trigger);
}
}
}
Quartz.net not allow use constructor with DI. That's why I'm trying to create property with DI.
ReminderJob.cs
public class ReminderJob : IJob
{
static IBot _bot;
public IBot Bot { get; set; }
public async Task Execute(IJobExecutionContext context)
{
var parameters = context.JobDetail.JobDataMap;
var userId = parameters.GetLongValue(ReminderJobConst.ChatId);
var homeWorkId = parameters.GetLongValue(ReminderJobConst.HomeWordId);
await System.Console.Out.WriteLineAsync("HelloJob is executing.");
}
}
How can I pass _botClient to reminderJob in Program.cs?
If somebody looks for answer, I have one:
Program.cs (in Main)
var schedBor = servicesProvider.GetRequiredService<ISchedulerBot>();
var logger = servicesProvider.GetRequiredService<ILogger<DIJobFactory>>();
schedBor.StartScheduler();
schedBor.Scheduler.JobFactory = new DIJobFactory(logger, servicesProvider);
DIJobFactory.cs
public class DIJobFactory : IJobFactory
{
static ILogger<DIJobFactory> _logger;
static IServiceProvider _serviceProvider;
public DIJobFactory(ILogger<DIJobFactory> logger, IServiceProvider sp)
{
_logger = logger;
_serviceProvider = sp;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
IJobDetail jobDetail = bundle.JobDetail;
Type jobType = jobDetail.JobType;
try
{
_logger.LogDebug($"Producing instance of Job '{jobDetail.Key}', class={jobType.FullName}");
if (jobType == null)
{
throw new ArgumentNullException(nameof(jobType), "Cannot instantiate null");
}
return (IJob)_serviceProvider.GetRequiredService(jobType);
}
catch (Exception e)
{
SchedulerException se = new SchedulerException($"Problem instantiating class '{jobDetail.JobType.FullName}'", e);
throw se;
}
}
// get from https://github.com/quartznet/quartznet/blob/139aafa23728892b0a5ebf845ce28c3bfdb0bfe8/src/Quartz/Simpl/SimpleJobFactory.cs
public void ReturnJob(IJob job)
{
var disposable = job as IDisposable;
disposable?.Dispose();
}
}
ReminderJob.cs
public interface IReminderJob : IJob
{
}
public class ReminderJob : IReminderJob
{
ILogger<ReminderJob> _logger;
IBot _bot;
public ReminderJob(ILogger<ReminderJob> logger, IBot bot)
{
_logger = logger;
_bot = bot;
}
public async Task Execute(IJobExecutionContext context)
{
var parameters = context.JobDetail.JobDataMap;
var userId = parameters.GetLongValue(ReminderJobConst.ChatId);
var homeWorkId = parameters.GetLongValue(ReminderJobConst.HomeWordId);
await _bot.Send(userId.ToString(), "test");
}
}

Testing controller with xUnit

I have webapi where it needs to call some other endpoint and get data.
My current code as follows
//http client implementation
public interface IHttpClientFactory
{
HttpClient Create();
}
public class HttpClientFactory : IHttpClientFactory
{
private readonly ApplicationSettings _applicationSettings;
HttpClient _httpClient;
public HttpClientFactory(IOptions<ApplicationSettings> settings)
{
_applicationSettings = settings.Value;
}
public HttpClient Create()
{
if (_httpClient != null)
return _httpClient;
var client = new HttpClient()
{
BaseAddress = new Uri($"{_applicationSettings.BaseUrl}")
};
_httpClient = client;
return _httpClient;
}
}
public interface IGetItemsQuery
{
Task<IEnumerable<T>> Execute<T>(string url);
}
public class GetItemQuery: IGetItemsQuery
{
private readonly IHttpClientFactory _httpClientFactory;
public GetPhotosQuery(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<IEnumerable<T>> Execute<T>(string url)
{
using (var response = await _httpClientFactory.Create().GetAsync($"{url}").ConfigureAwait(false))
{
response.EnsureSuccessStatusCode();
var resp = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var items = JArray.Parse(resp);
return items.ToObject<T[]>();
}
}
In my controller part
private readonly IGetItemsQuery _getItemsQuery;
public HomeController(IGetItemsQuery getItemsQuery)
{
_getItemsQuery = getItemsQuery;
}
appsettings
"ApplicationSettings": {
"BaseUrl": "http://someendpoint.com/"
}
Startup
services.Configure<ApplicationSettings>(Configuration.GetSection("ApplicationSettings"));
services.AddScoped<IGetItemsQuery, GetPhotosQuery>();
services.AddScoped<IHttpClientFactory, HttpClientFactory>();
I want to try something like below in my test
[Fact]
public void Test_Index()
{
// Arrange
var itemsQuery = new Mock<IGetItemsQuery>();
var controller = new HomeController(itemsQuery.Object);
// Act
var result = controller.Index();
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
Assert.Null(viewResult.ViewName);
}
This is creating mock IGetItemsQuery but this isn't mocking the actual IHttpClientFactory.
Is there a way to do this
Based on your design with the abstracted dependencies there would be no need to mock a client factory in order to unit test the controller.
As you have done in your test, you mock IGetItemsQuery, but you have not set it up to behave as expected when invoked in the test.
If, for example, the controller method under test look something like this
private readonly IGetItemsQuery getItemsQuery;
public HomeController(IGetItemsQuery getItemsQuery) {
this.getItemsQuery = getItemsQuery;
}
public async Task<IActionResult> Index() {
var url = "....";
var items = await getItemsQuery.Execute<MyItem>(url);
return View(items);
}
Then an isolated unit test for the Index action as the method under test could look something like
[Fact]
public async Task Index_Should_Return_View_With_Items() {
// Arrange
var itemsQuery = new Mock<IGetItemsQuery>();
var items = new MyItem[] {
new MyItem(),
new MyItem()
};
itemsQuery.Setup(_ => _.Execute<MyItem>(It.IsAny<string>()))
.ReturnsAsync(items);
var controller = new HomeController(itemsQuery.Object);
// Act
var result = await controller.Index();
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
Assert.Null(viewResult.ViewName);
}

Categories

Resources