In this module we are going to unit test the new filter feature. This should be becoming old hat by now. Let’s get going and wrap up this feature so we can finally move on to the Advanced Search in the SideNav.
Filter Unit Test 1: ApiResult --> CanFilter
Open the ApiResultTests.cs file in the FredsCarsAPI.Tests project and add the CanFilter()
test by modifying its contents with the code below in bold blue font.
FredsCarsAPI.Tests\Data\ApiResultTests.cs
using FredsCarsAPI.Data;
using MockQueryable.Moq;
namespace FredsCarsAPI.Tests.Data
{
public class TestPageData
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
public class ApiResultTests
{
private List<TestPageData> _data = new List<TestPageData>()
{
new TestPageData { Id = 1, Name = "Sam", },
new TestPageData { Id = 2, Name = "George" },
new TestPageData { Id = 3, Name = "Scott" },
new TestPageData { Id =4, Name = "James" },
new TestPageData { Id =5, Name = "Melissa" },
new TestPageData { Id =6, Name = "Ferris" },
new TestPageData { Id =7, Name = "Debrah" },
new TestPageData { Id =8, Name = "John" },
new TestPageData { Id =9, Name = "Greg" },
new TestPageData { Id =10, Name = "Sarah" },
new TestPageData { Id = 11, Name = "Mike" },
new TestPageData { Id = 12, Name = "Larry" },
new TestPageData { Id = 13, Name = "Emily" },
new TestPageData { Id = 14, Name = "Jane" }
};
/*** existing code ***/
#region filter tests
[Fact]
public async Task CanFilter()
{
// Arrange
// 1- Create the mock DbSet from the test data
var dataDbSet = _data.AsQueryable().BuildMockDbSet();
// 2- Convert the DbSet to an IQueryable interface
var dataIQueryable = dataDbSet.Object.AsQueryable();
// Act
var response = await ApiResult<TestPageData>.CreatAsync(
dataIQueryable, 0, 4, "Name", "asc", "name", "g");
// Assert
Assert.True(response.Data[0].Id == 2
&& response.Data[0].Name == "George"
&& response.Data[1].Id == 9
&& response.Data[1].Name == "Greg");
}
#endregion
}
}
In the CanFilter()
test code above, we have the same build up as we did for paging and sorting unit tests except that here in the Act section we pass a filterColumn of “name” and a filterValue of “g” in lower case; just like we pass “j” for Jeep, or “c” for Compass in the running application.
Then we assert that we should get two TestPageData objects back with Name values of “George” and “Greg”.
Open Test Explorer, right click on the new CanFilter()
unit test, and select run.

The above screen shows that the test unexpectedly fails. But why? It works from the applicaton. Let’s see if we can get to the bottom of this.
Put a breakpoint at the end of the filtering section in ApiResult.cs and run the application in debug mode. Then enter the character “j” in the filter input field like we are searching for Jeeps.

In the above screenshot we see the when we inspect the IQueryable source variable while the application is halted at the breakpoint, that there are the four results as we would expect, and that the actual type of the source IQueryable is:
Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable
<FredsCarsAPI.Models.DTOs.VehicleDTO>
The source IQueryable is using Entity Framework to query VehicleDTO objects. And Entity Framework is interpreting the StartsWith()
operator in our Dynamic LINQ query to be case insensitive.
Now, stop the appication, leave the break point where it is and in Test Explorer right click on the CanFilter()
unit test and select debug.

Now while the unit test is halted at the breakpoint, if we again inspect the IQueryable source variable, we can see that now the results are empty while in our assert statement we were expecting two records back for George and Greg. And, we can also notice that the type of the IQueryable source variable is not Microsoft.EntityFrameworkCore
, but MockQueryable.EntityFrameworkCore
.
Let’s nip this in the bud and fix the test right now. Or, rather fix the bug we have uncovered in the application code.
Open the ApiResult.cs file in the FredsCarsAPI project and modify its code with the contents below in bold blue font.
FredsCarsAPI/Data/ApiResult.cs
/*** existing code ***/
if (!string.IsNullOrEmpty(filterColumns)
&& !string.IsNullOrEmpty(filterValue)
&& filterColumnArray.ToList().All(fc => IsValidProperty(fc))
)
{
string queryString = string.Empty;
foreach (var fc in filterColumnArray)
{
queryString += string.Format("{0}.ToUpper().StartsWith(@0) || ",
fc);
}
queryString = queryString.Substring(0, queryString.Length - 4);
source = source.Where(queryString, filterValue.ToUpper());
}
/*** existing code ***/
In the code above we have made a couple simple changes just to ensure that we are comparing our strings from the data and the value of the filterValue parameter in the same case. To accomplish this we just append the C# ToUpper()
string method to both the {0} text placeholder in the text formatted queryString (which will end up looking something like “make.ToUpper()”) and to the filterValue parameter.
If you run the test now it should pass in Test Explorer with a green checkmark.
Also at this point, feel free to run the application and test the filtering again to make certain it still works.
So at this point hopefully you are starting to see the value to unit testing. Although our application was working fine. We did find a little bug. This might not ever rear its ugly head in our application unless, we ever decide to go to another database besides SQL Server that does not have case insensitive behavior by default like Oracle.
Filter Unit Test 2: VehiclesControler --> CanFilter
Open the VehiclesControllerTests.cs file and make the modifications to its code shown below in bold blue font.
FredsCarsAPI.Tests\Controllers\VehiclesControllerTests.cs
using Moq;
using MockQueryable.Moq;
using FredsCarsAPI.Controllers;
using FredsCarsAPI.Repositories;
using FredsCarsAPI.Models;
using FredsCarsAPI.Models.DTOs;
using FredsCarsAPI.Data;
namespace FredsCarsAPI.Tests.Controllers
{
public class VehiclesControllerTests
{
private List<Vehicle> _testData =
new List<Vehicle>
{
new Vehicle { Id = 1,
Status = Status.New,
Year = "2022",
Make = "M1",
Model = "M1",
Color = "C1",
Price = 64000,
VIN = "123",
VehicleTypeId = 1,
VehicleType = new VehicleType { Id = 1, Name = "Car" }
},
new Vehicle { Id = 2,
Status = Status.New,
Year = "2022",
Make = "M2",
Model = "M2",
Color = "C2",
Price = 64000,
VIN = "456",
VehicleTypeId = 2,
VehicleType = new VehicleType { Id = 2, Name = "Truck" }
},
new Vehicle { Id = 3,
Status = Status.New,
Year = "2022",
Make = "M3",
Model = "M3",
Color = "C3",
Price = 64000,
VIN = "789",
VehicleTypeId = 3,
VehicleType = new VehicleType { Id = 3, Name = "Jeep" }
},
new Vehicle { Id = 4,
Status = Status.New,
Year = "2022",
Make = "M4",
Model = "M4",
Color = "C4",
Price = 64000,
VIN = "012",
VehicleTypeId = 1,
VehicleType = new VehicleType { Id = 1, Name = "Car" }
},
new Vehicle { Id = 5,
Status = Status.New,
Year = "2022",
Make = "M5",
Model = "M5",
Color = "C5",
Price = 64000,
VIN = "345",
VehicleTypeId = 2,
VehicleType = new VehicleType { Id = 2, Name = "Truck" }
},
new Vehicle { Id = 6,
Status = Status.New,
Year = "2022",
Make = "M6",
Model = "M6",
Color = "C6",
Price = 64000,
VIN = "678",
VehicleTypeId = 3,
VehicleType = new VehicleType { Id = 3, Name = "Jeep" }
},
new Vehicle { Id = 7,
Status = Status.New,
Year = "2022",
Make = "M7",
Model = "M7",
Color = "C7",
Price = 64000,
VIN = "901",
VehicleTypeId = 1,
VehicleType = new VehicleType { Id = 1, Name = "Car" }
},
new Vehicle { Id = 8,
Status = Status.New,
Year = "2022",
Make = "M8",
Model = "M8",
Color = "C8",
Price = 64000,
VIN = "234",
VehicleTypeId = 2,
VehicleType = new VehicleType { Id = 2, Name = "Truck" }
},
new Vehicle { Id = 9,
Status = Status.New,
Year = "2022",
Make = "M9",
Model = "M9",
Color = "C9",
Price = 64000,
VIN = "567",
VehicleTypeId = 3,
VehicleType = new VehicleType { Id = 3, Name = "Jeep" }
},
new Vehicle { Id = 10,
Status = Status.New,
Year = "2022",
Make = "M10",
Model = "M10",
Color = "C10",
Price = 64000,
VIN = "890",
VehicleTypeId = 1,
VehicleType = new VehicleType { Id = 1, Name = "Car" }
},
new Vehicle { Id = 11,
Status = Status.New,
Year = "2022",
Make = "M11",
Model = "M11",
Color = "C11",
Price = 64000,
VIN = "abc",
VehicleTypeId = 2,
VehicleType = new VehicleType { Id = 2, Name = "Truck" }
},
new Vehicle { Id = 12,
Status = Status.New,
Year = "2022",
Make = "M12",
Model = "M12",
Color = "C12",
Price = 64000,
VIN = "def",
VehicleTypeId = 3,
VehicleType = new VehicleType { Id = 3, Name = "Jeep" }
},
new Vehicle { Id = 13,
Status = Status.New,
Year = "2022",
Make = "M13",
Model = "M13",
Color = "C13",
Price = 64000,
VIN = "ghi",
VehicleTypeId = 1,
VehicleType = new VehicleType { Id = 1, Name = "Car" }
},
new Vehicle { Id = 14,
Status = Status.New,
Year = "2022",
Make = "M14",
Model = "M14",
Color = "C14",
Price = 64000,
VIN = "jkl",
VehicleTypeId = 2,
VehicleType = new VehicleType { Id = 2, Name = "Truck" }
},
};
/*** existing code ***/
#region Filter Tests
[Fact]
public async Task CanFilter()
{
// Arrange
// Create the mock from the test data
Mock<IVehicleRepository> mockVehicleRepo =
new Mock<IVehicleRepository>();
var mockVehicleIQueryable =
_testData.AsQueryable().BuildMock();
mockVehicleRepo.Setup(m => m.Vehicles).Returns(mockVehicleIQueryable);
var controller = new VehiclesController(mockVehicleRepo.Object);
// Act
ApiResult<VehicleDTO> result =
await controller.GetVehicles(filterColumns: "vehicleType",
filterValue: "Jeep");
Assert.Equal(4, result.Data.Count());
}
#endregion
}
}
In the unit test code above for CanFilter()
, we use the same basic build up in the Arrange and Act sections.
In the Arrange section we create a mock VehicleRepo using the standard moq libraray. Next, we create a Mock Vehicle IQueryable using the MockQueryable extensions library and the _testData class member field which is a list of VehicleDTO test data. Then we set up the Mock VehicleRepo to return via its Vehicles Property defined in the IVehicleRepository Interface the data from the Mock Vehicle IQueryable to return the test data full of VehicleDTOs. Then we instantiate an instance of the Vehicles controller and pass to its constructor the Mock Vehicle Repo containing the test VehicleDTO test data.
In the Act section we call the Vehicle controller’s GetVehicles()
method and pass it two values using named parameters for filterColumns and filterValue of “vehicleType” as the filter column and “Jeep” as the filter value. We were able to use named parameters here since pageIndex and pageSize both have default values in the GetVehicles() parameter list of 0 and 10 respectively making them optional parameters. sortColumn and sortOrder are also optional parameters with default values of null. Since all four of the leading parameters are optional we can skip their values in the GetVehicles()
call and just provide the filter values we are interested in testing.
In the Act section we just make one assertion that the Count of the Data property in the response is four.
Go ahead and right click on the CanFilter unit test under FredsCarsAPI.Tests.Controller in Test Explorer and select Run. The test should succeed and you should see a green checkmark to the left of it.

Let’s go ahead and run all the tests from the command line and make sure they all succeed for good measure before we move on to the SideNav Advanced Search in the next module.
Run the following commands to navigate to the FredsCarsAPI.Tests project and use the dotnet test command to run all of the tests we have to date to make sure everything is still in good working order.
cd C:\Development\FredsCars\FullStack\Module30\FredsCarsAPI.Tests
dotnet test

In the screenshot above I have run the dotnet test
command against the FredsCarsAPI.Tests project and all 12 of our tests were successful. Our project is in good working order and we are free to continue on to add some more cool features for our users!
What’s Next
It’s almost time to start looking at how Forms work in Angular for user input. This will allow us to start to create edit, details, and delete pages for the Vehicle records displayed on the Vehicles page. And eventually Angular Forms may also help us to improve our code for the filter Quick Search and make it easier to code the Advanced Search SideNav Panel.
But first, let’s at least try to get the Category search working in the SideNav we worked so hard to setup in earlier modules using the existing patterns we have so far. This will be our task in Module 31. Searching by category using MatCheckbox in the SideNav.