ASP.NET Core & EF Core 2.0 Testing (2024)

Problem

This post will show you how to perform unit and integration testing of ASP.NET Core and EF Core.

Solution

Note: The sample code contains a lot more tests, I would suggest to download and play with it. Here, I will list a few tests to demonstrate how testing works.

Testing MVC

Add MVC controller with action methods:

C#

public IActionResult Index() { var model = service.GetMovies(); var viewModel = ToViewModel(model); return View(viewModel); } public IActionResult Edit(int id) { var model = service.GetMovie(id); if (model == null) return NotFound(); var viewModel = ToViewModel(model); return View("CreateOrEdit", viewModel); } [HttpPost] public IActionResult Save(int id, MovieViewModel viewModel) { if (viewModel == null) return BadRequest(); if (!ModelState.IsValid) return View("CreateOrEdit", viewModel); var model = ToDomainModel(viewModel); if (viewModel.IsNew) service.AddMovie(model); else service.UpdateMovie(model); return RedirectToAction("Index"); }

Add test to verify ViewResult is returned:

C#

[Fact(DisplayName = "Index_returns_ViewResult_and_model")] public void Index_returns_ViewResult_and_model() { // Arrange var mockService = new Mock<IMovieService>(); mockService.Setup(service => service.GetMovies()).Returns(new List<Movie>()); var sut = new HomeController(mockService.Object); // Act var result = sut.Index(); // Assert var viewResult = Assert.IsType<ViewResult>(result); var viewModel = Assert.IsType<List<MovieInfoViewModel>>(viewResult.Model); }

Add test to verify status code result (e.g. NotFound) is returned:

C#

[Fact(DisplayName = "Edit_with_invalid_Id_returns_NotFound")] public void Edit_with_invalid_Id_returns_NotFound() { // Arrange var mockService = new Mock<IMovieService>(); mockService.Setup(service => service.GetMovie(It.IsAny<int>())).Returns((Movie)null); var sut = new HomeController(mockService.Object); // Act var result = sut.Edit(0); // Assert Assert.IsType<NotFoundResult>(result); }

Add test to verify RedirectToAction is returned:

[Fact(DisplayName = "Save_with_new_model_calls_AddMovie_and_returns_RedirectToAction")] public void Save_with_new_model_calls_AddMovie_and_returns_RedirectToAction() { // Arrange var mockService = new Mock<IMovieService>(); var sut = new HomeController(mockService.Object); // Act var result = sut.Save(1, new MovieViewModel() { IsNew = true }); // Assert mockService.Verify(service => service.AddMovie(It.IsAny<Movie>()), Times.Once); var redirectResult = Assert.IsType<RedirectToActionResult>(result); Assert.Equal(expected: "Index", actual: redirectResult.ActionName); }

Add a test to verify ModelState errors don’t save and return back the view:

C#

[Fact(DisplayName = "Save_with_invalid_model_state_returns_ViewResult_and_model")] public void Save_with_invalid_model_state_returns_ViewResult_and_model() { // Arrange var mockService = new Mock<IMovieService>(); var sut = new HomeController(mockService.Object); sut.ModelState.AddModelError("Title", "Title is required"); // Act var result = sut.Save(1, new MovieViewModel()); // Assert var viewResult = Assert.IsType<ViewResult>(result); var viewModel = Assert.IsType<MovieViewModel>(viewResult.Model); }

Testing API

Add API controller with action methods:

C#

[HttpGet] public IActionResult Get() { var model = service.GetMovies(); var outputModel = ToOutputModel(model); return Ok(outputModel); } [HttpPost] public IActionResult Create([FromBody]MovieInputModel inputModel) { if (inputModel == null) return BadRequest(); if (!ModelState.IsValid) return Unprocessable(ModelState); var model = ToDomainModel(inputModel); service.AddMovie(model); var outputModel = ToOutputModel(model); return CreatedAtRoute("GetMovie", new { id = outputModel.Id }, outputModel); }

Add a test to verify OkObjectResult is returned:

C#

[Fact(DisplayName = "Get_retruns_OkObjectResult_and_model")] public void Get_retruns_Ok_result_and_model() { // Arrange var mockService = new Mock<IMovieService>(); mockService.Setup(service => service.GetMovies()).Returns(new List<Movie>()); var sut = new MoviesController(mockService.Object); // Act var result = sut.Get(); // Assert var okObjectResult = Assert.IsType<OkObjectResult>(result); var outputModel = Assert.IsType<List<MovieOutputModel>>(okObjectResult.Value); }

Add a test to verify CreatedAtRouteResult is returned:

[Fact(DisplayName = "Create_with_valid_model_calls_AddMovie_and_returns_CreatedAtRoute")] public void Create_with_valid_model_calls_AddMovie_and_returns_CreatedAtRoute() { // Arrange var mockService = new Mock<IMovieService>(); var sut = new MoviesController(mockService.Object); // Act var result = sut.Create(new MovieInputModel()); // Assert mockService.Verify(service => service.AddMovie(It.IsAny<Movie>()), Times.Once); var createAtRouteResult = Assert.IsType<CreatedAtRouteResult>(result); Assert.Equal(expected: "GetMovie", actual: createAtRouteResult.RouteName); }

Testing EF

Add a repository (implementation in sample code):

C#

public interface IMovieRepository { void Delete(int id); MovieEntity GetItem(int id); List<MovieEntity> GetList(); void Insert(MovieEntity entity); void Update(MovieEntity entity); }

The repository will work with a DbContext:

C#

public class Database : DbContext { public Database( DbContextOptions<Database> options) : base(options) { } public DbSet<MovieEntity> Movies { get; set; } }

Initialise with test data:

C#

private void InitDbContext(Database context) { context.Movies.Add(new MovieEntity { ... }); context.Movies.Add(new MovieEntity { ... }); context.Movies.Add(new MovieEntity { ... }); context.SaveChanges(); }

Now you could test various methods of repository, e.g. test GetList() method:

C#

[Fact(DisplayName = "GetList_returns_correct_count")] public void GetList_returns_correct_count() { // Arrange var builder = new DbContextOptionsBuilder<Database>(); builder.UseInMemoryDatabase(databaseName: "GetList_returns_correct_count"); var context = new Database(builder.Options); InitDbContext(context); var repo = new MovieRepository(context); // Act var result = repo.GetList(); // Assert Assert.Equal(expected: 3, actual: result.Count); }

Integration Testing

Create a base class for integration test classes:

C#

public class IntegrationTestsBase<TStartup> : IDisposable where TStartup : class { private readonly TestServer server; public IntegrationTestsBase() { var host = new WebHostBuilder() .UseStartup<TStartup>() .ConfigureServices(ConfigureServices); this.server = new TestServer(host); this.Client = this.server.CreateClient(); } public HttpClient Client { get; } public void Dispose() { this.Client.Dispose(); this.server.Dispose(); } protected virtual void ConfigureServices(IServiceCollection services) { } }

Create a controller to test MVC/API:

C#

public class MoviesControllerIntegration : IntegrationTestsBase<Startup> { [Fact(DisplayName = "Get_retruns_Ok")] public async Task Get_retruns_Ok_status_code() { // Arrange // Act var response = await this.Client.GetAsync("api/movies"); // Assert Assert.Equal(expected: HttpStatusCode.OK, actual: response.StatusCode); var outputModel = response.ContentAsType<List<MovieOutputModel>>(); Assert.Equal(expected: 2, actual: outputModel.Count); }

Discussion

The single biggest selling point of MVC architecture in general and ASP.NET Core in particular is that it makes testing much simpler. ASP.NET team has done a great job in making a framework that is pluggable, thus enabling testing of controllers, repositories and even the entire application a breeze.

Unit Testing

Unit Testing ASP.NET Core and API controllers is not very different than testing any other class in your application. The sample code contains a lot more tests to show examples of type of tests you could perform, e.g.:

  • Verify correct IActionResult is returned, e.g. ViewResult, RedirectAtRouteResult
  • Verify correct view name is returned
  • Verify correct model is returned
  • Verify correct HTTP status code is returned e.g. NotFoundResult, BadRequestResult
  • Verify model state behaviour e.g. not saving record and returning the view.
  • Verify controller dependencies are being called.

Testing Entity Framework

You could test EF using in-memory database, you’ll need package Microsoft.EntityFrameworkCore.InMemory that gives you UseInMemoryDatabase extension method on DbContextOptionsBuilder. With these pieces in place, you could now create an in-memory DbContext:

C#

var builder = new DbContextOptionsBuilder<Database>(); builder.UseInMemoryDatabase( databaseName: "GetList_returns_correct_count"); var context = new Database(builder.Options); InitDbContext(context); var repo = new MovieRepository(context);

Integration Testing

Remember that ASP.NET Core application is just a console application that sets up web server to listen to HTTP requests. We can setup a test web server using TestServer class and use HttpClient to send requests to it:

C#

public IntegrationTestsBase() { var host = new WebHostBuilder() .UseStartup<TStartup>() .ConfigureServices(ConfigureServices); this.server = new TestServer(host); this.Client = this.server.CreateClient(); } public HttpClient Client { get; }
ASP.NET Core & EF Core 2.0 Testing (2024)

References

Top Articles
Latest Posts
Article information

Author: Virgilio Hermann JD

Last Updated:

Views: 5950

Rating: 4 / 5 (41 voted)

Reviews: 80% of readers found this page helpful

Author information

Name: Virgilio Hermann JD

Birthday: 1997-12-21

Address: 6946 Schoen Cove, Sipesshire, MO 55944

Phone: +3763365785260

Job: Accounting Engineer

Hobby: Web surfing, Rafting, Dowsing, Stand-up comedy, Ghost hunting, Swimming, Amateur radio

Introduction: My name is Virgilio Hermann JD, I am a fine, gifted, beautiful, encouraging, kind, talented, zealous person who loves writing and wants to share my knowledge and understanding with you.