How should unit test look in ASP.NET Core app for controllers? And I have a database SQL Server and my app is connected with my app by EF Core. My views are .cshtml
files and in Index
method it depends on the data from db. For example it is one of my controllers (HomeController). Also I have services and models in my app and views. I don't know is it a right way to test services or controllers? And how?
public class HomeController : Controller
{
// ...
[Route("home")]
public IActionResult Index()
{
var lst = _service.GetAll();
return View(lst);
}
[Route("about")]
public IActionResult About()
{
return View();
}
}
How to write a test for this? I know that this way is not correct but I don't know another...
[Test]
public async Task CheckHome_SendRequest_ShouldReturnOk()
{
// Arrange
WebApplicationFactory<Startup> webHost = new WebApplicationFactory<Startup>()
.WithWebHostBuilder(_ => { });
HttpClient httpClient = webHost.CreateClient();
// Act
HttpResponseMessage httpResponse = await httpClient.GetAsync("home");
// Assert
Assert.AreEqual(HttpStatusCode.OK, httpResponse.StatusCode);
}
Edit: it can be other type of tests
CodePudding user response:
Alright, so if I'm getting this correctly, the system under test here is the Controller.
So, first thing (that you are already doing) would be to have a decent DI and inject controller's dependencies into the Controller, for example, via contstructor.
Then, you would need to build your DI container and your mocking mechanism such that the Controller
instance would be instantiated with mocked dependencies.
Then, you simply ask your DI to give you said instance and make a direct call to its method (or methods) that you are testing.
And then you Assert
on either outcome of the controller's method's result, or what methods on the injected mocked dependencies the controller called, or both.
P.S. So, there is no need for HttpClient
. By using HttpClient
and making an actual web request you test (and also make your test dependable upon) lots of unneccesary stuff, like the network layer of the operation system, pipeline of the asp.net framework, and so on.
CodePudding user response:
One solution is to use an inmemory database using Microsoft.EntityFrameworkCore.InMemory nuget package.
My unit test setup looks like this:
[SetUp]
public void Setup()
{
var options = new DbContextOptionsBuilder<MyContext>()
.UseInMemoryDatabase("MyInMemoryTestDb")
.Options;
_db = new MyContext(options); //the ef core db context
_service = new ServiceIWantToTest(_db);
MockData(); //this adds some testable data to _db using regular EF Core methods
}
[TearDown]
public void TearDown()
{
_db.Database.EnsureDeleted();
}
CodePudding user response:
you could create a base class for initializing your testserver
public abstract class TestBase : IDisposable
{
private bool _databaseInitialized;
private HttpClient? _client;
private TestServer TestServer { get; }
public TestBase(Action<IServiceCollection>? testServicesConfiguration = null)
{
// create a test server instance
TestServer = CreateServer(testServicesConfiguration ?? (s => { }));
}
public static TestServer CreateServer(Action<IServiceCollection> testServicesConfiguration)
{
var hostBuilder = new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration(cb =>
{
cb.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
})
.ConfigureTestServices(testServicesConfiguration)
.UseStartup<Startup>()
.ConfigureAppConfiguration((_, builder) =>
{
builder.AddUserSecrets<Program>(optional: true);
});
return new TestServer(hostBuilder);
}
public async Task<HttpResponseMessage> GetAsync(string requestUri) =>
await GetClient().GetAsync(requestUri);
private HttpClient GetClient()
{
// singleton
return TestServer.CreateClient();
}
}
And create a new inherit class from testBase for the test sets:
public class GetScenarioIT : TestBase
{
protected virtual string TestEndpoint() => "admin/home";
[Fact]
public async Task Get_and_response_ok_status_code()
{
// Act
var response = await GetAsync(TestEndpoint());
// Assert
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}