public Fixture()
{
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureServices(services =>
{
services.AddScoped<ICarService, CarService>();
}));
Client = _server.CreateClient();
}
from tests I'm using this HttpClient to test my API.
using (var response = await _client.GetAsync($"/api/car/{id}"))
{
//...
}
The thing is that I want to fake the result of the GetAsync(int id) method in CarService class.
So I tried
var myCarObject = ... omitted for clarity
var myCarMockService = new Mock<ICarService>();
myCarMockService.Setup(x => x.GetAsync(It.IsAny<int>())).Returns(Task.FromResult(myCarObject));
I don't know is this right approach, but if it is how can I inject it
into Fixture class so CarService can use it.
public class CarService: ICarService {
private readonly CarDbContext _carDbContext;
public CarService(CarDbContext carDbContext)
{
_carDbContext = carDbContext;
}
public async Task<Car> GetAsync(int id)
{
return await _carDbContext.Cars.FindAsync(id);
}
}
Update:
private readonly ICarService _carService;
public CarController(ICarService carService)
{
_carService = carService;
}
public async Task<IActionResult> Get([FromRoute] int id)
{
var car = await _carService.GetAsync(id);
}
Update 2:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<CarDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("Db")); });
services.AddTransient<ICarService, CarService>();
}
}
public class CarService : ICarService
{
private readonly CarDbContext _carDbContext;
public ContactService(CarDbContext carDbContext)
{
_carDbContext= carDbContext;
}
public async Task<Owner> GetAsync(int ownerId)
{
var owner = await _carDbContext.Owners.FindAsync(ownerId);
return owner.Car;
}
}
Update 3:
private readonly TestServer _server;
public Fixture()
{
var dbContextOptions = new DbContextOptionsBuilder<CarDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var mockContext = new Mock<CarDbContext>(dbContextOptions);
var mockOwnerSet = new Mock<DbSet<Owner>>();
var mockCarSet = new Mock<DbSet<Car>>();
mockContext.Setup(m => m.Owners).Returns(mockOwnerSet.Object);
mockContext.Setup(m => m.Cars).Returns(mockCarSet.Object);
var carService = new CarService(mockContext.Object);
_server = new TestServer(new WebHostBuilder()
.ConfigureAppConfiguration((context, conf) =>
{
conf.AddJsonFile(#Directory.GetCurrentDirectory() + "../appsettings.json");
}).UseStartup<Startup>()
.ConfigureServices(services =>
{
services.AddDbContext<CarDbContext>(options => options.UseInMemoryDatabase("Test"));
services.AddScoped<ICarService>(_ => carService);
})
);
Client = _server.CreateClient();
}
Configure the test server to use the mocked service
public Fixture() {
Car myCarObject = //... omitted for brevity
var myCarMockService = new Mock<ICarService>();
myCarMockService
.Setup(x => x.GetAsync(It.IsAny<int>()))
.ReturnsAsync(myCarObject);
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureTestServices(services => {
var serviceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(ICarService));
if (serviceDescriptor != null) services.Remove(serviceDescriptor);
services.AddTransient<ICarService>(_ => myCarMockService.Object); // <-- NOTE
})
);
Client = _server.CreateClient();
}
That way when the call is made the mocked service will be injected as expected.
Related
I'm unit testing the following service and the issue is that I'm able to test only the happy path but not the unhappy path. So how do I test them? TestScheduler? http://introtorx.com/Content/v1.0.10621.0/16_TestingRx.html
These lines are left untested:
.Catch<string, TimeoutExceptiocn>(_ => Observable.Return("Timeout"))
.Catch<string, Exception>(ex => Observable.Return(ex.Message))
public interface IProductService
{
Task<string> GetAsync();
}
public sealed class ProductService : IProductService
{
private readonly HttpClient _httpClient;
public ProductService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public Task<string> GetAsync()
{
return Observable
.FromAsync(() => _httpClient.GetAsync("http://www.mocky.io/v2/5e307edf3200005d00858b49"))
.SubscribeOn(TaskPoolScheduler.Default)
.Retry(5)
.Timeout(TimeSpan.FromSeconds(5))
.Do(x => Console.WriteLine($"Is message successful? {x.IsSuccessStatusCode}"))
.SelectMany(async responseMessage =>
{
var response = await responseMessage.Content.ReadAsStringAsync();
return response;
})
.Catch<string, TimeoutException>(_ => Observable.Return("Timeout"))
.Catch<string, Exception>(ex => Observable.Return(ex.Message))
.ToTask();
}
}
public class ProductServiceTests
{
[Fact]
public async Task GetAsync_ShouldReturnText_WhenRequestIsSent()
{
// Arrange
var messageHandler = new MockHttpMessageHandler("Stuff I want to return", HttpStatusCode.OK);
var httpClient = new HttpClient(messageHandler);
var sut = new ProductService(httpClient);
// Act
var result = await sut.GetAsync();
// Assert
result.Should().Be("Stuff I want to return");
}
// TODO: Unhappy path
}
I try to do integrations tests with WebApplicationFactory, but I get error.
Part of Program.cs:
builder.AddNegotiate(options =>
{
var ldapConnectionsFactory = new LdapConnectionsFactory(domainConfiguration, loggerFactory.CreateLogger<LdapConnectionsFactory>());
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && ldapConnectionsFactory.TryCreate(out var ldapConnection))
{
options.EnableLdap(settings =>
{
settings.LdapConnection = ldapConnection;
settings.Domain = domainConfiguration.Domain;
});
}
});
This code add six services to services array. If I don't add builder.AddNegotiate, I don't have problem.
When I try to do test, I get error:
Negotiate authentication requires a server that supports IConnectionItemsFeature like Kestrel.
TestFile:
[TestFixture]
public class AuthControllerTests
{
readonly CustomWebApplicationFactory<Program> _factory;
private readonly HttpClient _client;
public AuthControllerTests()
{
_factory = new CustomWebApplicationFactory<Program>();
_client = _factory.CreateClient();
_client.BaseAddress = new Uri("http://localhost:8001/");
}
[Test]
public async Task CheckAdminLogIn_SendRequest_ShouldReturnOk()
{
// Arrange
var credentials = new Credentials() { Login = "admin", Password = "admin" };
var jsonSerializerOptions = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNameCaseInsensitive = true
};
string jsonString = JsonSerializer.Serialize(credentials, jsonSerializerOptions);
StringContent httpContent = new StringContent(jsonString, System.Text.Encoding.UTF8, "application/json");
// Act
var response = await _client.PostAsync("api/Auth/login", httpContent);
// Assert
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
I try remove services in CustomWebApplicationFactory:
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Here I am trying to find and delete Negotiate, but it deletes 4 out of 6 services
var negotiateHandler = services.SingleOrDefault(d => d.ServiceType == typeof(NegotiateHandler));
services.Remove(negotiateHandler);
List<ServiceDescriptor> servicesForRemove = services.Where(d => d.ServiceType.FullName.Contains("Negotiate")).ToList();
foreach (var s in servicesForRemove)
{
services.Remove(s);
}
});
}
}
It is not removed:
Is there another way to disable negotiate auth?
I removed all Microsoft.AspNetCore.Authentication, then added JwtBearer. It works for me
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
List<ServiceDescriptor> servicesForRemove = services.Where(d => d.ServiceType.FullName.Contains("Microsoft.AspNetCore.Authentication")).ToList();
foreach (var s in servicesForRemove)
{
services.Remove(s);
}
var buildServiceProvider = services.BuildServiceProvider();
var loggerFactory = buildServiceProvider.GetService<ILoggerFactory>();
services.AddAuthentication().AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = JwtProvider.CreateTokenValidator(loggerFactory);
});
});
}
}
You can remove a specific scheme with:
internal class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.Configure<AuthenticationOptions>(o =>
{
if (o.Schemes is List<AuthenticationSchemeBuilder> schemes)
{
schemes.RemoveAll(s => s.Name == NegotiateDefaults.AuthenticationScheme);
o.SchemeMap.Remove(NegotiateDefaults.AuthenticationScheme);
}
});
});
}
}
I'm trying to mock the configuration, but urlVariable keeps returning null, I also could not mock GetValue since it's static extionsion under Configuration Builder
public static T GetValue<T>(this IConfiguration configuration, string key);
Here's what I tried so far
// Arrange
var mockIConfigurationSection = new Mock<IConfigurationSection>();
mockIConfigurationSection.Setup(x => x.Value).Returns("SomeUrl");
mockIConfigurationSection.Setup(x => x.Key).Returns("Url");
var configuration = new Mock<IConfiguration>();
configuration.Setup(c => c.GetSection(It.IsAny<String>())).Returns(mockIConfigurationSection.Object);
// Act
var result = target.Test();
The method
public async Task Test()
{
var urlVariable = this._configuration.GetValue<string>("Url");
}
trying to mock these from app settings
{
"profiles": {
"LocalDB": {
"environmentVariables": {
"Url" : "SomeUrl"
}
}
}
}
Maybe you are not instantiating target properly. This piece of code should work.
void Main()
{
// Arrange
var mockIConfigurationSection = new Mock<IConfigurationSection>();
mockIConfigurationSection.Setup(x => x.Value).Returns("SomeUrl");
mockIConfigurationSection.Setup(x => x.Key).Returns("Url");
var configuration = new Mock<IConfiguration>();
configuration.Setup(c => c.GetSection(It.IsAny<String>())).Returns(mockIConfigurationSection.Object);
var target = new TestClass(configuration.Object);
// Act
var result = target.Test();
//Assert
Assert.Equal("SomeUrl", result);
}
public class TestClass
{
private readonly IConfiguration _configuration;
public TestClass(IConfiguration configuration) { this._configuration = configuration; }
public string Test()
{
return _configuration.GetValue<string>("Url");
}
}
Also, you might want to explore OptionsPattern
You don't need to mock something which can be set created manually.
Use ConfigurationBuilder to setup expected values.
[Fact]
public void TestConfiguration()
{
var value = new KeyValuePair<string, string>(
"profiles:LocalDb:environmentVariable:Url",
"http://some.url"
);
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new[] { value })
.Build();
var actual =
configuration.GetValue<string>("profiles:LocalDb:environmentVariable:Url");
actual.Should().Be("http://some.url");
}
I'm trying to write an Unit Test for my ASP.Net Core application with XUnit framework and MOQ and am trying to test the below method(snippet given below):
public async Task<IActionResult> Save([FromBody] DTO.ContactUs contactUs)
{
contactUs.FirstName = _htmlEncoder.Encode(contactUs.FirstName);
contactUs.LastName = _htmlEncoder.Encode(contactUs.LastName);
contactUs.EmailAddress = _htmlEncoder.Encode(contactUs.EmailAddress);
contactUs.Phone = _htmlEncoder.Encode(contactUs.Phone);
if (HttpContext.User.CurrentClient() != null)
contactUs.ClientId = HttpContext.User.CurrentClient().ClientId;
contactUs.UserId = User.GetUserId();
string dbName = HttpContext.User.CurrentClient().ConnectionString;
var result = _clientService.AddNewContactUs(contactUs, dbName);
if (result)
{
try
{
int clientId = HttpContext.User.CurrentClient().ClientId;
var clientDetails = _clientService.GetClientDetailsByClientID(clientId);
// Lines of code...
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
return Json(result);
}
While I can mock all the other dependent services, I'm kind of stuck with the HttpContext part. I am not able to mock the HttpContext.User.CurrentClient() part where HttpContext.User is of type ClaimsPrincipal and CurrentClient is an user-defined function, defined as:
public static Client CurrentClient(this ClaimsPrincipal principal)
{
if (!string.IsNullOrEmpty(principal.Claims.Single(p => p.Type.Equals(AppClaimTypes.CurrentClient)).Value))
{
int clientId = Convert.ToInt32(principal.Claims.Single(p => p.Type.Equals(AppClaimTypes.CurrentClient)).Value);
return principal.GetClients().Where(c => c.ClientId == clientId).FirstOrDefault();
}
else
{
return null;
}
}
This is my UnitTest class that I have managed to write till now:
public class ContactUsControllerTests
{
private Mock<IClientService> clientServiceMock;
private Mock<IWebHostEnvironment> webHostEnvironmentMock;
private Mock<HtmlEncoder> htmlEncoderObjMock;
private Mock<IEmailNotification> emailNotificationMock;
private Mock<HttpContext> mockContext;
private Mock<HttpRequest> mockRequest;
private Mock<ClaimsPrincipal> mockClaimsPrincipal;
private ContactUs contactUsObj = new ContactUs()
{
FirstName = "TestFN",
LastName = "TestLN",
EmailAddress = "testemail#gmail.com",
Phone = "4564560000",
Comments = "This is just a test"
};
private ClaimsPrincipal principal = new ClaimsPrincipal();
public ContactUsControllerTests()
{
clientServiceMock = new Mock<IClientService>();
webHostEnvironmentMock = new Mock<IWebHostEnvironment>();
htmlEncoderObjMock = new Mock<HtmlEncoder>();
emailNotificationMock = new Mock<IEmailNotification>();
mockRequest = new Mock<HttpRequest>();
mockContext = new Mock<HttpContext>();
// set-up htmlEncoderMock
htmlEncoderObjMock.Setup(h => h.Encode(contactUsObj.FirstName)).Returns(contactUsObj.FirstName);
htmlEncoderObjMock.Setup(h => h.Encode(contactUsObj.LastName)).Returns(contactUsObj.LastName);
htmlEncoderObjMock.Setup(h => h.Encode(contactUsObj.EmailAddress)).Returns(contactUsObj.EmailAddress);
htmlEncoderObjMock.Setup(h => h.Encode(contactUsObj.Phone)).Returns(contactUsObj.Phone);
htmlEncoderObjMock.Setup(h => h.Encode(contactUsObj.Comments)).Returns(contactUsObj.Comments);
// set-up mockContext
mockContext.Setup(m => m.Request).Returns(mockRequest.Object);
mockContext.Object.User.CurrentClient().ClientId = 30; // this throws error
//other initialisations
}
[Fact]
public async void SaveMethodTest()
{
ContactUsController contactUsControllerObj = new ContactUsController(clientServiceMock.Object, webHostEnvironmentMock.Object, htmlEncoderObjMock.Object, emailNotificationMock.Object);
// Act
await contactUsControllerObj.Save(contactUsObj);
// Arrange
// Lines of code
}
}
Any help whatsoever on this would very helpful.
I have an api which is using the httpclientfactory to create different typed httpclients using services.addhttpclient<>(). I've started to integrate hangfire with my service due to long running jobs. Everything was working fine until I tried to use Hangfires schdule method "BackgroundJob.Schedule". It starts up, schedules the task but when it tries to execute the code I get:
"Unable to resolve service for type 'System.Net.Http.HttpClient' while attempting to activate 'BackupApi.BackupApiService'."
When trying to use Enqueue method it works without problem. Kinda lost atm, all help much appreciated. My guess is that somehow when the task has scheduled the dependencies is lost when hangfire later tries to use:
using BackupApi;
var backupApiService = Activate<BackupApiService>();
await backupApiService.AdhocBackup("BlurredServername");
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<IBackupApiService, BackupApiService>()
.ConfigurePrimaryHttpMessageHandler(handler =>
new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }
});
services.AddHangfire(configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions
{
SchemaName = "BackupApi",
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
UsePageLocksOnDequeue = true,
DisableGlobalLocks = true
}));
services.Configure<ConnectionInfo>(Configuration.GetSection("ConnectionStrings"));
services.AddHttpContextAccessor();
//services.AddTransient<IBackupApiService, BackupApiService>()
var section = Configuration.GetSection("ConnectionStrings");
services.AddHttpClient<DSDClient>()
.ConfigurePrimaryHttpMessageHandler(handler =>
new HttpClientHandler
{
Credentials = new NetworkCredential(
section["Username"],
section["Password"],
"blurredomain"),
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }
});
services.AddHangfireServer();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
//.AddViewComponentsAsServices();
}
BackupApiService.cs
public class BackupApiService : IBackupApiService
{
public HttpClient _netclient { get; }
private static IOptions<ConnectionInfo> _config;
public DSDClient _dsdclient { get; }
private readonly IHttpContextAccessor _httpContextAccessor;
public BackupApiService(IOptions<ConnectionInfo> config, HttpClient netclient, DSDClient dsdclient, IHttpContextAccessor httpContextAccessor)
{
_config = config;
_httpContextAccessor = httpContextAccessor;
_dsdclient = dsdclient;
_netclient = netclient;
_netclient.Timeout = new TimeSpan(0, 2, 30);
_netclient.DefaultRequestHeaders.Clear();
_netclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", _config.Value.NetworkerConnectionString);
_netclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<string> AdhocBackup(string ServerName)
{
....
}
BackupApiV1Controller
public class BackupApiV1Controller : ControllerBase
{
private readonly IBackupApiService _backupApiService;
public BackupApiV1Controller(IBackupApiService backupApiService)
{
_backupApiService = backupApiService;
}
[HttpPost]
[Route("StartDateBackup/")]
public IActionResult StartDateBackup([FromBody] ServerSchedule serverSchedule)
{
var resp = BackgroundJob.Schedule(() => _backupApiService.AdhocBackup(serverSchedule.Servername), serverSchedule.Date);
return Ok();
}
[HttpPost]
[Route("StartAdhocBackup/")]
public IActionResult StartAdhocBackup([FromBody] Server server)
{
var resp = BackgroundJob.Enqueue(() => _backupApiService.AdhocBackup(server.Servername));
return Ok(resp);
}