I have this in the ConfigureServices method:
services.AddHttpClient("ClientUsingCredentials")
.ConfigurePrimaryHttpMessageHandler(() =>
{
var credentials = new NetworkCredential("someUsername", "somePassword");
return new HttpClientHandler()
{
// UseDefaultCredentials = true,
Credentials = credentials
};
});
Now, my service looks like this:
public class WebAppService : IWebAppService
{
private readonly ILogger<WebAppService> _logger;
private readonly IHttpClientFactory _httpClientFactory;
public WebAppService(ILogger<WebAppService> logger, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
}
public async Task<WebAppReport> CheckWebSiteWithCredentialsAsync(string someUrlThatNeedsCredentials)
{
WebAppReport report = new();
try
{
using (var client = _httpClientFactory.CreateClient("ClientUsingCredentials"))
{
var result = await client.GetAsync(someUrlThatNeedsCredentials);
if (result.IsSuccessStatusCode)
{
// Do something with the result
report.IsCheckSuccessful = true;
}
else
{
// Set report fields accordingly
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"Something went wrong while checking the website: '{someUrlThatNeedsCredentials}'.");
// Set report fields accordingly
}
return report;
}
}
The WebAppReport class:
public class WebAppReport
{
public bool IsCheckSuccessful { get; set; }
//Some other fields here
}
Is there a way to write unit test for this service method that also checks that it passes correct credentials while sending out requests?
Edit: I see #Nkosi's answer saying that Unit testing is not possible in this case. So, can you please show an example for doing integration test on it?
Should I be creating the service in my test method using new ServiceCollection(), add the service in the same way I did in ConfigureServices method, set correct credentials and so on? Would love to see your example on it.
Thank you!
Is there a way to write unit test for this method that also checks that it passes correct credentials while sending out requests?
Short answer: No. Not in a unit test.
That is because the ConfigurePrimaryHttpMessageHandler is a framework cross-cutting concern that is only applied by the actual HttpClientFactory at run time when creating the actual client that will be used to make the request.
You are basically trying to test that the HttpClientFactory implementation will behave as designed. Microsoft would have tested that before releasing it for use.
For your specific scenario, something like that would ideally need to be done in an integration test.
For testing your WebAppService you should focus on the logic local to the subject under test.
Related
I have two tests of an ASP.NET core webapp within a single test class. The class uses an IClassFixture<WebApplicationFactory<Startup>>.
The first test is a happy path test: when a message arrives on a Kafka topic, some file appears in a particular output directory.
The second test is to verify that if something goes wrong with outputting said file, the webapp should then be in an unhealthy status.
For brevity, I'm omitting the code that publishes to Kafka and that generates the output folder the application saves files to - neither of these are relevant.
[CollectionDefinition("e2e", DisableParallelization = true)]
[Trait("Category", "IntegrationTest")]
public class EndToEndTest : IClassFixture<WebApplicationFactory<Startup>>, IClassFixture<KafkaFixture>
{
private readonly WebApplicationFactory<Startup> _factory;
public EndToEndTest(WebApplicationFactory<Startup> factory)
{
_factory = factory;
KafkaUtils.Publish(SomeMessage()).GetAwaiter().GetResult();
}
[Fact]
public void WhenXReceived_ThenFileIsOutput()
{
var options = OutputFolder();
Directory.CreateDirectory(options.Path);
RunService(services => services.AddSingleton(Options.Create(options))).CreateClient();
Thread.Sleep(10_000);
var outputDirectory = Directory.GetFiles(options.Path);
Assert.Single(outputDirectory);
var file = outputDirectory.Single();
Assert.NotEmpty(File.ReadAllLinesAsync(file).GetAwaiter().GetResult());
Directory.Delete(options.Path, true);
}
[Fact]
public void WhenFileTransferFails_ThenAppShouldBeUnhealthy()
{
var options = OutputFolder();
// only try once, so we don't have to wait
var retry = new RetryPolicyConfiguration {OnErrorRetryCount = 1};
var factory = RunService(services =>
{
services.AddSingleton(Options.Create(options));
services.AddSingleton(Options.Create(retry));
services.RemoveAll<IFileWriter>();
services.AddScoped<IFileWriter, ThrowingFileWriter>();
});
var client = factory.CreateClient();
Thread.Sleep(10_000);
Assert.False(Directory.Exists(options.Path));
var response = client.GetAsync("/health/ready").GetAwaiter().GetResult();
var stream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
using var reader = new StreamReader(stream);
var body = reader.ReadToEndAsync().GetAwaiter().GetResult();
var health = JsonConvert.DeserializeObject<HealthCheckResult>(body);
Assert.NotEqual(HealthStatus.Healthy, health.Status);
}
protected WebApplicationFactory<Startup> RunService(Action<IServiceCollection> serviceConfig)
{
return _factory
.WithWebHostBuilder(builder => builder
.ConfigureTestServices(services =>
{
serviceConfig(services);
services.AddSubscribeBus(new ConfigurationBuilder()
.AddJsonFile(ConfigMapFileProvider.FromRelativePath("config"),
"appsettings.e2e.json", true, true)
.Build());
}));
}
}
public class ThrowingFileWriter : IFileWriter
{
public void Write(string fileName, envelope envelope)
{
throw new Exception("foo");
}
public void Delete(string fileName)
{
}
}
If I run WhenFileTransferFails_ThenAppShouldBeUnhealthy() (unhealthyTest from here on, for brevity) on its own, then the test passes. But if I run the entire test class, then WhenXReceived_ThenFileIsOutput() (healthyTest, for brevity) runs before it, and for some reason unhealthyTest seems to use the service context that was created for healthyTest.
I've tried my best to work out what's going on, but it doesn't make sense to me. When I debug through it, the order of events is roughly as follows:
healthyTest starts
Startup invoked for healthytest
Constructor of service that uses IFileWriter is invoked with real instance of service
healthyTest completes
unhealthyTest starts
Constructor of service that uses IFileWriter is invoked with real instance of service (???)
Startup invoked for unhealthyTest
serviceConfig action invoked for unhealthyTest: all implementations of IFileWriter replaced with ThrowingFileWriter - but this is too late, it's already been constructed with the real instance in step 6
Consequently, the real service is used instead of the throwing service, and the exception handler that sets the app status to Unhealthy is never invoked.
Originally this was all running asynchronously, but I thought that perhaps the async nature of the tests meant that the two were conflicting with the webapp created by the factory - hence all the GetAwaiter().GetResult().
What am I doing wrong in my setup of the webapp for the test?
Note: it's unfortunately absolutely not an option to move these tests into their own test classes.
I'm building an application which uses ASP Core Blazor WebAssembly and ASP Core MVC for the backend. This allows for a lot of code re-use, however I'm a bit lost when it comes to sharing API specifics (routes, HTTP method and return values). gRPC solves this perfectly but has some drawbacks. Is there anyway to solve this with pure C#?
Consider this following basic controller:
[ApiController]
[Route("[controller]")]
public class PostController : ControllerBase
{
private readonly ILogger<PostController> logger;
private ApplicationDbContext context;
public PostController(ILogger<PostController> logger, ApplicationDbContext dbContext)
{
this.logger = logger;
context = dbContext;
}
[HttpGet("list")]
[AllowAnonymous]
public async Task<ActionResult<IEnumerable<SelectListOption<int>>>> List()
{
var posts = await context.Posts.ToListAsync();
return posts.OrderBy(e => e.Name == "Other").ThenBy(e => e.Name).Select(e => new SelectListOption<int> { Id = e.Id, Name = e.Name });
}
}
On the WebAssembly (client) side, I have a basic class which calls the corresponding Apis:
public class PostClient : IPostClient
{
private readonly HttpClient client;
private const string endpoint = "Post";
public PostClient(HttpClient client)
{
this.client = client;
}
public async Task<IList<SelectListOption<int>>> ListPostsAsync()
{
try
{
var result = await client.GetFromJsonAsync<List<SelectListOption<int>>>($"{endpoint}/list");
if (result == null) throw new Exception();
return result;
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
return new List<SelectListOption<int>>();
}
}
There are a couple of problems with this:
The string "Post" is hardcoded. There is no guarantee that this controller exists on the backend. Similarly, the path for the specific action (/lists) is also hardcoded. There is no guarantee this path is actually defined and would not be apparent until runtime. I'm looking for a solution that can guarantee that all api paths in the client are defined correctly on the backend during compile-time, run-time (during startup), or in unit tests.
My first thought was adding an interface (in a shared assembly) which is implemented by PostController, it would just define the endpoint and return values.
interface IPostApi
{
Task<IEnumerable<SelectListOption<int>>> List();
}
class PostController : IPostApi
{
//...
}
However this does not work because PostController.List needs to implement ActionResult which is not compatible with IPostApi which does not have ActionResult.
It seems like the only way to do this is to use integration tests or other tests which iterate through all the clients and apis and verify the routes and data types match up. Is there anyway to have this sort of stuff checked at compile time?
There is not. APIs are in their nature http and independent from the client side.
Be that as it may, a good way to do this, is to generate the open api json file, and auto generate the client side https://learn.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?view=aspnetcore-3.1. Check https://github.com/OpenAPITools/openapi-generator for a library to do that.
This is similar to wsdl for soap services.
Does any one know how to write a unit test (using xUnit) for the following Get() Method?
Get() method is in the controller and returns list of all Categories:
public class CategoryController : Controller
{
private MyContext x;
public CategoryController(MyContext y)
{
x = y;
}
[HttpGet]
public ActionResult<IEnumerable<Category>> Get()
{
return x.Categories.ToList();
}
}
If you are using EFCore as ORM, you can use InMemory database for unit testing.
There simple example:
[Fact]
public void TestGet()
{
_options = new DbContextOptionsBuilder<MyContext>()
.UseInMemoryDatabase(databaseName: "default")
.Options;
var context = new MyContext(_options);
context.EnsureSeed();
var controller = new CategoryController(context);
//Act
var results = controller.Get();
//Assert
Assert.NotNull(results);
Assert.True(results.Count > 0, "Expected to be greater than 0.");
}
Also you need implement EnsureSeed method. Example:
public static void EnsureSeed(this MyContext dataContext)
{
//Check if database is created, if not - create
dataContext.Database.EnsureCreated();
var category = new Category()
{
Id = 1
};
dataContext.Categories.Add(category);
dataContext.SaveChanges();
}
From what I've seen and read the best way to unit test a controller function is to create an instance of the server host from your test setup and make requests directly to your endpoint - this will allow you test the transport layer of your application like the API contract and Http protocols.
The following is an example implemented in .Net Core:
[Trait]
public class CategoryControllerTests : IClassFixture<WebApplicationFactory<Startup>>
{
// Startup - the entry point of most .net core project
private readonly WebApplicationFactory<Startup> _factory;
public CategoryControllerTests(WebApplicationFactory<Startup> factory)
{
// Any webhost config needed to run tests against the test
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
// register any mock dependancies there - any dependencies in Startup.cs will hold unless overridden by a mock
services.AddScoped(x => new Mock() );
});
});
}
[Fact]
public async Task Get_ValidRequest_ReturnsData()
{
var client = _factory.CreateClient();
// Whatever routing protocol you use to define your endpoints
var url = "/category";
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsAsync<List<Category>>();
// Any asserts on your response
Assert.NotNull(content);
}
}
This is dependant on how you have setup the startup/initialisation of your project, it will allow you to test the project as though it were running in a production environment while letting you mock out any dependancies below the transport layer for a true unit test.
Note: the use of IClassFixture<WebApplicationFactory> - this will let you reuse an instance of WebApplicationFactory<Startup> for faster test execution; XUnit will inject this for you as part of the framework.
I have a controller:
public class InvitationsController: Controller {
private readonly IMapper _mapper;
private readonly IInvitationManager _invitationManager;
private readonly UserManager<MyAppUser> _userManager;
public InvitationsController(
IInvitationManager invitationManager,
IMapper mapper,
UserManager<MyAppUser> userManager,
IJobManager jobManager
) {
_invitationManager = invitationManager;
_mapper = mapper;
_userManager = userManager;
}
[Authorization]
GetInvitationByCode(string code) { ... }
I'm trying to write unit tests using Xunit and Moq. Here is the implentation of my test:
public class InvitationsControllerTests {
private Mock<IInvitationManager> invitationManagerMock;
private Mock<UserManager<MyAppUser>> userManagerMock;
private Mock<IMapper> mapperMock;
private InvitationsController controller;
public InvitationsControllerTests() {
invitationManagerMock = new Mock<IInvitationManager>();
userManagerMock = new Mock<UserManager<MyAppUser>>();
mapperMock = new Mock<IMapper>();
controller = new InvitationsController(invitationManagerMock.Object,
mapperMock.Object,
userManagerMock.Object);
}
[Fact]
public async Task GetInvitationByCode_ReturnsInvitation() {
var mockInvitation = new Invitation {
StoreId = 1,
InviteCode = "123abc",
};
invitationManagerMock.Setup(repo =>
repo.GetInvitationByCodeAsync("123abc"))
.Returns(Task.FromResult(mockInvitation));
var result = await controller.GetInvitationByCode("123abc");
Assert.Equal(mockInvitation, result);
}
I don't think I'm using the mocking functionality correctly. Specifically with UserManager. I can't find a clear answer on using Moq to test controllers protected by [Authorize]. When running my tests, it throws an exception on
controller = new InvitationsController(invitationManagerMock.Object,
mapperMock.Object,
userManagerMock.Object);
Which reads:
Castle.DynamicProxy.InvalidProxyConstructorArgumentsException: 'Can not instantiate proxy of class: Microsoft.AspNetCore.Identity.UserManager`1[[MyApp.api.Core.Models.MyAppUser, MyApp.api, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].
Could not find a parameterless constructor.'
You're not unit testing; you're integration testing. When you find yourself setting up ten thousand mocks just to run a method, that's a pretty good sign it's an integration test. Additionally, things like authorization only happen as part of the request lifecycle; there's no way to test that, without doing an actual request, which again, means you're integration testing.
As such, use the test host.
private readonly TestServer _server;
private readonly HttpClient _client;
public MyTestClass()
{
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
_client = _server.CreateClient();
}
[Fact]
public async Task GetInvitationByCode_ReturnsInvitation() {
var mockInvitation = new Invitation {
StoreId = 1,
InviteCode = "123abc",
};
var response = await _client.GetAsync("/route");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<Invitation>(responseString);
// Compare individual properties you care about.
// Comparing the full objects will fail because of reference inequality
Assert.Equal(mockInvitation.StoreId, result.StoreId);
}
If you need to scaffold your data to make the correct result return, simply use the in-memory database provider. The easiest way to use this for integration testing is to specify a new environment like "Test". Then, in your startup, when configuring your context, branch on the environment and use the in-memory provider (instead of SQL Server or whatever) when the environment is "Test". Then, when setting up your test server for integration testing, simply add .UseEnvironment("Test") before .UseStartup<Startup>().
I think, problem is in dependency injection. In your Startups.cs file you could find similar string: services.AddIdentity<AppUser, AppRole>().AddEntityFrameworkStores<AppDbContext>().AddDefaultTokenProviders(); it means that magic of namespace Microsoft.Extensions.DependencyInjection provide you an instance of your User- or RoleManger anywhere where you want to use it. For example, in InvitationsController using injectin in constructor.
You can try inject UserManger in test class and mock it. Or read similar question
I'm testing a WCF service methods using specflow and nunit; my scenarios look like the following:
Feature: GetAccount
Testing API method 'get account'
Background:
Given Server is running
Scenario: Succesful Get
Given An Existing Account
When I call the GetAccount API method With password = "123"
Then the result should be Success
I'm not sure on how to implement the background step;
The server can be run as console / windows service using Topshelf-
private static void Main()
{
Host host = HostFactory.New(config =>
{
config.Service<ServiceInitializer>(service =>
{
service.ConstructUsing(s => new ServiceInitializer());
service.WhenStarted((s, control) => s.Start(control));
service.WhenStopped((s, control) => s.Stop(control));
});
config.RunAsPrompt();
});
host.Run();
}
public class ServiceInitializer : ServiceControl
{
private readonly ILog m_log;
public ServiceInitializer()
{
log4net.Config.XmlConfigurator.Configure();
m_log = LogManager.GetLogger("Server");
}
public bool Start(HostControl hostControl)
{
try
{
var host = new IoCServiceHost(typeof(MyService));
host.Open();
m_log.Info("Server is now open.");
return true;
}
catch (Exception exception)
{
m_log.Fatal("Initialization of service failed",exception);
return false;
}
}
public bool Stop(HostControl hostControl)
{
m_log.Info("Server has closed");
return true;
}
}
should I just execute the .exe service file, or can I use my ServiceInitializer in some way? perhaps I could use nUnit's [SetUpFixture]?
Are there any Specflow best practices?
Let's consider what you want to test.
Do you need to test that Windows correctly runs services?
Do you need to test that Topshelf correctly starts services?
Or do you just want to test that GetAccount works?
I'll bet that you are using Topshelf to make your life easier, so do that and trust that their code works within windows. Its a valid assumption since there code will be used in many places and they probably have their own test suites, and if your assumption is wrong, then test it later when you find the problems.
So all you really need is
[BeforeFeature]
public void Background()
{
FeatureContext.Current["Host"] =new MyHostObject();
}
[When("I call GetAccount API method with password =\"(\.*)\"")]
public void WhenICallGetAccount(string password)
{
var host = (MyHostObject)FeatureContext.Current["Host"];
ScenarioContext.Current["Account"] = host.GetAccount(password);
}
[Then("the result should be success")]
public void ThenTheResultShouldBeSuccessful()
{
var account = (MyAccount)ScenarioContext.Current["Account"];
//assuming using Should;
account.ShouldNotBeNull();
}