I am building a small web API using .NET Core. I want to practice TDD and this is my first attempt at TDD. These are a few use cases of the API:
- Users in administrator role can create/edit/delete common(shared) lesson records.
- Users in teacher role can create lesson record.
- Users in teacher role can edit or delete lesson records that are created by them.
- Lessons must have a name (cannot be empty).
So I now have something like this
using FluentValidation;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
[Route("/lessons")]
[Authorize(Roles = "teacher, admin")]
public class LessonsController : ApiControllerBase
{
private readonly IMediator _mediator;
public LessonsController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
[Authorize(Roles = "admin")]
[Route("create-common-lesson")]
public async Task<IActionResult> CreateCommonLesson([FromBody]CreateLessonCommand command)
{
command.UserId = null;
return await ExecuteRequest(_mediator, command);
}
[HttpPost]
[Route("create-lesson")]
public async Task<IActionResult> CreateLesson([FromBody]CreateLessonCommand command)
{
command.UserId = User.FindFirst("sub")?.Value;
return await ExecuteRequest(_mediator, command);
}
[HttpGet]
[Route("common-lessons")]
public async Task<IActionResult> GetCommonLessons()
{
var query = new LessonQuery();
return Ok(await _mediator.Send(query));
}
}
and here are some of my tests ;
using Common;
using FluentAssertions;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Xunit;
[Collection("Api Tests")]
public class LessonApiTests : IntegrationTestBase, IClassFixture<WebApiTestFixture>
{
public LessonApiTests(WebApiTestFixture fixture)
: base(fixture)
{
}
[Fact]
public async Task LessonApi_Get_Should_ReturnUnauthorized_When_TokenNotProvided()
{
Client.DefaultRequestHeaders.Authorization = null;
var response = await Client.GetAsync("/lessons/common-lessons");
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
[Fact]
public async Task LessonApi_GetCommonLessons_ReturnsCommonLesson_WhenTeacherRequests()
{
var response = await TeacherGetAsync("/lessons/common-lessons");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var lessons = await response.Content.ReadAsAsync<IEnumerable<Lesson>>();
lessons.Should().NotBeNullOrEmpty();
}
[Fact]
public async Task LessonApi_CreateCommonLesson_ReturnsForbidden_When_UserIsATeacher()
{
var model = new CreateLessonCommand()
{
Name = "Some-Random-Name"
};
var content = new StringContent(
JsonConvert.SerializeObject(model),
Encoding.UTF8,
"application/json");
var response = await TeacherPostAsync("/lessons/create-common-lesson", content);
response.StatusCode.Should().Be(HttpStatusCode.Forbidden);
}
[Fact]
public async Task LessonApi_CreateCommonLesson_Creates_When_UserIsAnAdminAndValidModelPosted()
{
var model = new CreateLessonCommand()
{
Name = "Some-Random-Name"
};
var content = new StringContent(
JsonConvert.SerializeObject(model),
Encoding.UTF8,
"application/json");
var response = await AdminPostAsync("/lessons/create-common-lesson", content);
response.StatusCode.Should().Be(HttpStatusCode.OK);
response = await TeacherGetAsync("/lessons/common-lessons");
var lessons = await response.Content.ReadAsAsync<IEnumerable<Lesson>>();
lessons.Should().Contain(l => l.Name == "Some-Random-Name");
}
[Fact]
public async Task LessonApi_CreateCommonLesson_ReturnsBadRequest_WhenEmptyNamePosted()
{
var model = new CreateLessonCommand();
var content = new StringContent(
JsonConvert.SerializeObject(model),
Encoding.UTF8,
"application/json");
var response = await AdminPostAsync("/lessons/create-common-lesson", content);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
response.Content.ReadAsStringAsync().Result.Should().Contain(Constants.ErrorCodes.Lesson.NameCannotBeEmpty);
}
[Fact]
public async Task LessonApi_CreateLesson_Creates_WhenValidModelProvided()
{
}
}
I am trying to write tests for every use case scenario. However, I feel like I am off track here.
My questions are:
Is writing tests for every use case scenario good practice or bad practice?
Should the name of the integration test explain the use case or should it explain how the API will behave? For example, should it be something like this:
LessonApi_CreateCommonLesson_Returns_BadRequest_When_EmptyNamePosted
or like this:
Admin_CannotCreateCommonLesson_When_NameIsEmpty