-
Notifications
You must be signed in to change notification settings - Fork 209
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #308 from hassanhabib/users/glhays/standard-v2110
STANDARD: Controllers v2.11.0
- Loading branch information
Showing
1 changed file
with
317 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,317 @@ | ||
# 2.5 Controllers | ||
|
||
## 2.5.0 Introduction | ||
In this section, we will cover the basics of controllers and how the Standard utilizes them in our api applications. Controllers are responsible for handling incoming HTTP requests, processing them, and returning appropriate responses. | ||
|
||
With The Standard version 2.11.0 we will introduce a key missing part to date, testing our controllers. This ensures that our api's are now completely covered with TDD(Test Driven Design). In the following sections we will discuss with samples the defining of our controller unit tests. | ||
|
||
|
||
## 2.5.1 Controller Structure | ||
Controllers are classes that are responsible for handling incoming HTTP requests, processing them, and returning appropriate responses. Controllers are the entry point for all incoming requests to the application. The Standard follows the MVC (Model-View-Controller) pattern, which means that controllers are responsible for handling the request, processing it, and returning the response. | ||
|
||
The Standard controllers are classes that inherit from RESTFulSenseController of the library RESTFulSense that returns meaningful exceptions. | ||
|
||
## 2.5.2 Controller Unit Testing | ||
|
||
In this section, we will discuss how to write unit tests for controllers. We will cover the basics of controller unit testing and how to write tests for controllers in The Standard. | ||
|
||
### Ensuring Controllers are doing their job | ||
The first step in writing unit tests for controllers is to ensure that the controllers are doing their job. This means that the controllers are handling incoming requests, processing them, and returning appropriate responses. | ||
To do this, we need to write tests that simulate incoming requests, check the responses and mapping particular exceptions categorical and localized to accurate exceptions.. | ||
|
||
For instance we may have the following local wrapped in a categorical exceprions: | ||
|
||
NotFoundStudentException -> StudentValidationException | ||
|
||
with this example we will not want to return a 200 status code with exception message of a not found. So appropriate mapping is required. | ||
|
||
### Writing Controller Unit Tests | ||
|
||
Firstly, we need to create a namespace within our Unit Test project named `Controllers\Students`. | ||
Controller tests follow the naming convention of the controller they are testing, followed by `Tests`. | ||
For example, if we have a controller named `StudentsController`, the base class will be named `StudentsControllerTests`. | ||
Subsequent partial classes will then be named `StudentsControllerTests.Logic.Post.cs` and so forth based on the Http controller action. | ||
|
||
|
||
#### Example `StudentsControllerTests` base class | ||
```csharp | ||
// ---------------------------------------------------------------------------------- | ||
// Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers | ||
// ---------------------------------------------------------------------------------- | ||
using System; | ||
using OTripleS.Core.Api.Controllers; | ||
using OTripleS.Core.Api.Models.Foundations.Students; | ||
using OTripleS.Core.Api.Models.Foundations.Students.Exceptions; | ||
using OTripleS.Core.Api.Services.Foundations.Students; | ||
using Moq; | ||
using RESTFulSense.Controllers; | ||
using Tynamix.ObjectFiller; | ||
using Xeptions; | ||
|
||
namespace OTripleS.Core.Api.Tests.Unit.Controllers.Students | ||
{ | ||
public partial class StudentsControllerTests : RESTFulController | ||
{ | ||
private readonly Mock<IstudentService> studentServiceMock; | ||
private readonly StudentsController studentsController; | ||
|
||
public StudentsControllerTests() | ||
{ | ||
this.studentServiceMock = new Mock<IstudentService>(); | ||
|
||
this.studentsController = new StudentsController( | ||
studentService: this.studentServiceMock.Object); | ||
} | ||
|
||
public static TheoryData<Xeption> ValidationExceptions() | ||
{ | ||
var someInnerException = new Xeption(); | ||
string someMessage = CreateRandomString(); | ||
|
||
return new TheoryData<Xeption> | ||
{ | ||
new StudentValidationException( | ||
message: someMessage, | ||
innerException: someInnerException), | ||
|
||
new StudentDependencyValidationException( | ||
message: someMessage, | ||
innerException: someInnerException) | ||
}; | ||
} | ||
|
||
public static TheoryData<Xeption> ServerExceptions() | ||
{ | ||
var someInnerException = new Xeption(); | ||
string someMessage = CreateRandomString(); | ||
|
||
return new TheoryData<Xeption> | ||
{ | ||
new StudentDependencyException( | ||
message: someMessage, | ||
innerException: someInnerException), | ||
|
||
new StudentServiceException( | ||
message: someMessage, | ||
innerException: someInnerException) | ||
}; | ||
} | ||
|
||
private static Student CreateRandomstudent() => | ||
CreateStudentFiller().Create(); | ||
|
||
private static string CreateRandomString() => | ||
new MnemonicString().GetValue(); | ||
|
||
private static DateTimeOffset GetRandomDateTimeOffset() => | ||
new DateTimeRange(earliestDate: new DateTime()).GetValue(); | ||
|
||
private static Filler<Student> CreatestudentFiller() | ||
{ | ||
var filler = new Filler<student>(); | ||
|
||
filler.Setup() | ||
.OnType<DateTimeOffset>().Use(GetRandomDateTimeOffset) | ||
.OnProperty(student => student.Teachers).IgnoreIt() | ||
.OnProperty(student => student.LibraryCards).IgnoreIt(); | ||
|
||
return filler; | ||
} | ||
} | ||
``` | ||
|
||
#### Example `StudentsControllerTests.Logic.Posts.cs` partial class | ||
```csharp | ||
// ---------------------------------------------------------------------------------- | ||
// Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers | ||
// ---------------------------------------------------------------------------------- | ||
using System.Threading.Tasks; | ||
using Force.DeepCloner; | ||
using OTripleS.Core.Api.Services.Foundations.Students; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Moq; | ||
using RESTFulSense.Clients.Extensions; | ||
using RESTFulSense.Models; | ||
|
||
namespace OTripleS.Core.Api.Tests.Unit.Controllers.Students | ||
{ | ||
public partial class StudentsControllerTests | ||
{ | ||
[Fact] | ||
public async Task ShouldReturnCreatedOnPostAsync() | ||
{ | ||
// given | ||
Student randomStudent = CreateRandomStudent(); | ||
Student inputStudent = randomStudent; | ||
Student addedStudent = inputStudent; | ||
Student expectedStudent = addedStudent.DeepClone(); | ||
|
||
var expectedObjectResult = | ||
new CreatedObjectResult(expectedStudent); | ||
|
||
var expectedActionResult = | ||
new ActionResult<Student>(expectedObjectResult); | ||
|
||
this.StudentServiceMock.Setup(service => | ||
service.AddStudentAsync(inputStudent)) | ||
.ReturnsAsync(addedStudent); | ||
|
||
// when | ||
ActionResult<Student> actualActionResult = | ||
await this.StudentsController.PostStudentAsync( | ||
inputStudent); | ||
|
||
// then | ||
actualActionResult.ShouldBeEquivalentTo( | ||
expectedActionResult); | ||
|
||
this.StudentServiceMock.Verify(service => | ||
service.AddStudentAsync(inputStudent), | ||
Times.Once); | ||
|
||
this.StudentServiceMock.VerifyNoOtherCalls(); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
#### Example `StudentsControllerTests.Exceptions.Posts.cs` partial class | ||
```csharp | ||
// ---------------------------------------------------------------------------------- | ||
// Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers | ||
// ---------------------------------------------------------------------------------- | ||
using System; | ||
using System.Threading.Tasks; | ||
using OTripleS.Core.Api.Models.Foundations.Students; | ||
using OTripleS.Core.Api.Models.Foundations.Students.Exceptions; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Moq; | ||
using RESTFulSense.Clients.Extensions; | ||
using RESTFulSense.Models; | ||
using Xeptions; | ||
|
||
namespace OTripleS.Core.Api.Tests.Unit.Controllers.Students | ||
{ | ||
public partial class StudentsControllerTests | ||
{ | ||
[Theory] | ||
[MemberData(nameof(ValidationExceptions))] | ||
public async Task ShouldReturnBadRequestOnPostIfValidationErrorOccurredAsync( | ||
Xeption validationException) | ||
{ | ||
// given | ||
Student someStudent = CreateRandomStudent(); | ||
|
||
BadRequestObjectResult expectedBadRequestObjectResult = | ||
BadRequest(validationException.InnerException); | ||
|
||
var expectedActionResult = | ||
new ActionResult<Student>(expectedBadRequestObjectResult); | ||
|
||
this.StudentServiceMock.Setup(service => | ||
service.AddStudentAsync(It.IsAny<Student>())) | ||
.ThrowsAsync(validationException); | ||
|
||
// when | ||
ActionResult<Student> actualActionResult = | ||
await this.StudentsController.PostStudentAsync(someStudent); | ||
|
||
// then | ||
actualActionResult.ShouldBeEquivalentTo(expectedActionResult); | ||
|
||
this.StudentServiceMock.Verify(service => | ||
service.AddStudentAsync(It.IsAny<Student>()), | ||
Times.Once); | ||
|
||
this.StudentServiceMock.VerifyNoOtherCalls(); | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(ServerExceptions))] | ||
public async Task ShouldReturnInternalServerErrorOnPostIfServerErrorOccurredAsync( | ||
Xeption validationException) | ||
{ | ||
// given | ||
Student someStudent = CreateRandomStudent(); | ||
|
||
InternalServerErrorObjectResult expectedBadRequestObjectResult = | ||
InternalServerError(validationException); | ||
|
||
var expectedActionResult = | ||
new ActionResult<Student>(expectedBadRequestObjectResult); | ||
|
||
this.StudentServiceMock.Setup(service => | ||
service.AddStudentAsync(It.IsAny<Student>())) | ||
.ThrowsAsync(validationException); | ||
|
||
// when | ||
ActionResult<Student> actualActionResult = | ||
await this.StudentsController.PostStudentAsync(someStudent); | ||
|
||
// then | ||
actualActionResult.ShouldBeEquivalentTo(expectedActionResult); | ||
|
||
this.StudentServiceMock.Verify(service => | ||
service.AddStudentAsync(It.IsAny<Student>()), | ||
Times.Once); | ||
|
||
this.StudentServiceMock.VerifyNoOtherCalls(); | ||
} | ||
|
||
[Fact] | ||
public async Task ShouldReturnConflictOnPostIfAlreadyExistsStudentErrorOccurredAsync() | ||
{ | ||
// given | ||
Student someStudent = CreateRandomStudent(); | ||
var someInnerException = new Exception(); | ||
string someMessage = CreateRandomString(); | ||
|
||
var alreadyExistsStudentException = | ||
new AlreadyExistsStudentException( | ||
message: someMessage, | ||
innerException: someInnerException, | ||
data: someInnerException.Data); | ||
|
||
var StudentDependencyValidationException = | ||
new StudentDependencyValidationException( | ||
message: someMessage, | ||
innerException: alreadyExistsStudentException); | ||
|
||
ConflictObjectResult expectedConflictObjectResult = | ||
Conflict(alreadyExistsStudentException); | ||
|
||
var expectedActionResult = | ||
new ActionResult<Student>(expectedConflictObjectResult); | ||
|
||
this.StudentServiceMock.Setup(service => | ||
service.AddStudentAsync(It.IsAny<Student>())) | ||
.ThrowsAsync(StudentDependencyValidationException); | ||
|
||
// when | ||
ActionResult<Student> actualActionResult = | ||
await this.StudentsController.PostStudentAsync(someStudent); | ||
|
||
// then | ||
actualActionResult.ShouldBeEquivalentTo(expectedActionResult); | ||
|
||
this.StudentServiceMock.Verify(service => | ||
service.AddStudentAsync(It.IsAny<Student>()), | ||
Times.Once); | ||
|
||
this.StudentServiceMock.VerifyNoOtherCalls(); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
## Conclusion | ||
|
||
With this brief introduction to controllers and how to write unit tests for them, we hope you have a better understanding of how controllers work in The Standard and how to write tests for them. | ||
|
||
By following the guidelines outlined in this section, you can ensure that your controllers are doing their job and returning appropriate responses. This will help you build robust and reliable api applications that are easy to maintain and test. | ||
|
||
|
||
For a further understanding and discussions please see the following video [GitFyle Youtube](https://youtu.be/Fc4LgUR2174) |