-
Notifications
You must be signed in to change notification settings - Fork 14
Tutorial 05 01 Unit Testing Generated OData Services
In this tutorial, you will learn how to generate, configure, and run unit tests for the OData controllers and endpoints that you are generating.
In order to complete this tutorial, you must have an existing Harmony Core solution that contains code-generated OData services, that you created by following either the Creating a Demo Service or Building a Service From Scratch tutorials.
The unit testing environment produced by this tutorial also supports custom authentication, so if you have also followed the Authentication via Custom Code, that's OK, things should still work here.
The first step in the process of adding unit testing capabilities to your Harmony Core development environment is to add a new project to your solution. In .NET Core unit tests are typically executed from the command line, using the command:
dotnet test
So to facilitate this, the new project that you will add to your solution will be a Synergy .NET Core Console Application. Because of existing logic that is defined in your regen.bat
file, the name of the new project should be Services.Test
.
-
Open Visual Studio and open your Harmony Core development solution.
-
In
Solution Explorer
, right-click on the main solution and selectAdd > New Project...
. -
Locate and select the project template for a
Synergy DBL Console App (.NET Core)
and click theNext
button. -
Set the
Project name
toServices.Test
then click theCreate
button. -
The project will contain a default source file named
Program.dbl
, rename it toSelfHost.dbl
.
Next, you need to check a couple of project configuration items, making sure that your project is targeting .NET Core 3.1, and also making the project subscribe to the Common Properties
that are already in use in the other projects in your solution.
-
In
Solution Explorer
, right-click on the new project and selectProperties
. -
In the
Application
tab, set theTarget framework
to.NET Core 3.1
-
In the
Common Properties
tab, check theUse common properties
option, then ensure that theCommon properties file location
is set to$(SolutionDir)Common.props
, and that you can see various values in the properties list. -
Select
File > Save All
from the menu, then close the properties window.
Now that you have a basic project in place, you will need to add references to several of the other projects in your solution, so that your unit testing code can have access to your startup configuration, controllers and models, etc.
-
In
Solution Explorer
, and within theServices.Test
project, right-click on theReferences
folder and selectAdd Reference...
-
On the left side of the
Reference Manager
dialog, select theProjects
node, then check the checkbox control next to the following projects:- Services
- Services.Controllers
- Services.Models
-
Click the
OK
button to close the dialog and add the references.
In addition to adding references to several local projects, you will also on this occasion need to add references to the various NuGet packages that will be required in order to implement unit testing.
Adding references to NuGet packages is completely normal in .NET Core development, where pretty much everything comes from NuGet. But you probably haven't been exposed to this yet when performing Harmony Core development, because until now, pretty much everything you have done has been in the context of existing pre-configured projects that were provided by the Harmony Core Solution Templates.
-
In
Solution Explorer
right click on the main solution and selectManage NuGet Packages for Solution…
. This will open a window with the tab titleNuget - Solution
-
In the upper-right corner of the dialog, check that the
Package source
dropdown is set tonuget.org
.
When considering the list of NuGet packages that will be required to implement the unit testing features that will be needed, those packages fall into two different categories. These categories are packages that are already used by other projects in the solution, and packages that are not. You will use a slightly different procedure to add those categories of packages.
As you proceed through the following steps, some packages that you add, regardless of whether they are already used in the solution or not, will display a popup dialog asking you to accept the license terms for the package. and depending on your Visual Studio configuration, sometimes you may see two dialogs displayed when to add a package. It's OK to just dismiss these dialogs. In fact, some have a don't show me this again
option, in case you want to suppress them from being displayed.
- In the
NuGet - Solution
window, near the top-left corner, if not already selected, select theInstalled
tab.
The list of packages that you will install from this UI is:
- Harmony.Core.AspNetCore
- HarmonyCore.CodeDomProvider
- IdentityModel
- Microsoft.EntityFrameworkCore
- Microsoft.NET.Test.Sdk
- Nito.AsyncEx
- System.Linq.DynamicCore
- System.Text.Encoding.CodePages
-
For each of the packages in the list:
- Locate and select the package in the list.
- In the list of projects near the top-right of the dialog, check the checkbox next to the
Services.Test.synproj
project. Once selected it should remain selected. - Click the
Install
button
As you add each package, you should see the package appear in Solution Explorer, in the Services.Test > References > Nuget
node.
The process for adding packages that are not already installed is similar, except that you must search for each package by name and then install it for the project.
- In the
Nuget - Solution
window, switch from theInstalled
tab to theBrowse
tab.
The list of packages that you will install from this UI is:
- Microsoft.AspNetCore.Mvc.Testing
- Microsoft.AspNetCore.OData
- Microsoft.AspNetCore.SignalR.Client
- Microsoft.Extensions.Logging.Console
- MSTest.TestAdapter
- MSTest.TestFramework
- NewtonSoft.Json
-
For each of the packages in the list:
- Type the package name into the
Search
control. - Wait for the list of package to update.
- Look for the package; it should be the top item but always check the name.
- Select the package.
- Check that
Services.Test.synproj
is still checked in the projects list. - Click the
Install
button.
- Type the package name into the
-
When you have added all of the packages, close the
Nuget - Solution
window.
The next step is to ensure that your new project is configured to use the correct versions of all of the NuGet packages, based on the latest version of Harmony Core. The versions of the packages that were already installed will be correct, but the versions of packages that were not already installed could potentially be higher than the versions currently used by the version of Harmony Core that you are using. This may or may not be a problem, but it's always safer to use versions of packages that match what Harmony Core was built against.
The easiest way to validate the versions of NuGet packages being used is to run the Harmony Core Project Upgrade Tool against your solution. To do so"
-
Use
Tools > Command Prompt (x64)
to open a command prompt window. -
In Visual Studio, use
File > Close Solution
to close the Visual Studio solution. -
Back in the command prompt window, move up one level to your main solution directory, set SolutionDir, and execute the
harmonycore upgrade-latest
command:cd .. set SolutionDir=%CD%\ harmonycore upgrade-latest
-
You will be required to enter
YES
to confirm the upgrade, and you should see output similar to this:Scanning 'D:\HC_WEBINAR_MyDemoApi' for HarmonyCore project files This utility will make significant changes to projects and other source files in your Harmony Core development environment. Before running this tool we recommend checking the current state of your development environment into your source code repository, taking a backup copy of the environment if you don't use source code control. Type YES to proceed: YES Updating template files in D:\HC_WEBINAR_MyDemoApi\Templates Found template files in D:\HC_WEBINAR_MyDemoApi\Templates\SignalR Found template files in D:\HC_WEBINAR_MyDemoApi\Templates\TraditionalBridge Updating traditional bridge files in D:\HC_WEBINAR_MyDemoApi\TraditionalBridge\Bridge
In this case, the primary purpose of running the project upgrade tool was to ensure that appropriate versions of all dependent packages are being used, based on the version of Harmony Core being used. But, if your solution happened to be using an old version of Harmony Core then it will have been updated to the latest version, and appropriate CodeGen templates and Traditional Bridge library code (if in use) will have been provided also.
- Leave the command prompt window open, and switch back to Visual Studio and re-open the solution.
As with many features in Harmony Core, most of the code required to implement basic unit testing can be generated using CodeGen and templates provided by Harmony Core. And, as usual, the generation of this code is enabled via an ENABLE_UNIT_TEST_GENERATION
option in regen.bat
.
-
Edit
regen.bat
then locate and remove therem
comment from theENABLE_UNIT_TEST_GENERATION
option, like thisset ENABLE_UNIT_TEST_GENERATION=YES
-
Save the changes to the file.
-
Switch back to your command prompt window and execute the
regen.bat
batch file. -
As always, check the
Output
window and look for theDONE
message to indicate that code generation was successful, then leave the command prompt window open.
Enabling the ENABLE_UNIT_TEST_GENERATION
option causes several new source files to be generated into your unit testing project:
-
Self-hosting code was generated into the existing
SelfHost.dbl
source fileThe code in the self hosting program is similar to the code in the main
Services.Host
project, except that it starts and configures a self-hosting instance specifically configured to support the execution of unit tests. -
Several new source files were created in the
Services.Test
project folder:-
TestConstants.Properties.dbl
This file defines various properties that are used by the unit tests to identify particular data entities during testing. All you will find here are the properties themselves. The values are defined in the next source file. The content of the file should look like this:
import Microsoft.VisualStudio.TestTools.UnitTesting import Newtonsoft.Json import System.Collections.Generic import System.Net.Http namespace Services.Test public static partial class TestConstants ;;------------------------------------------------------------ ;;Test data for Customer ;; public static readwrite property GetCustomers_Count, int public static readwrite property GetCustomer_CustomerNumber, int public static readwrite property GetCustomer_Expand_REL_Orders_CustomerNumber, int public static readwrite property GetCustomer_Expand_REL_Item_CustomerNumber, int public static readwrite property GetCustomer_Expand_All_CustomerNumber, int public static readwrite property GetCustomer_ByAltKey_State_State, String public static readwrite property GetCustomer_ByAltKey_Zip_ZipCode, int public static readwrite property GetCustomer_ByAltKey_PaymentTerms_PaymentTermsCode, String public static readwrite property UpdateCustomer_CustomerNumber, int ;;------------------------------------------------------------ ;;Test data for Item ;; public static readwrite property GetItems_Count, int public static readwrite property GetItem_ItemNumber, int public static readwrite property GetItem_Expand_REL_Vendor_ItemNumber, int public static readwrite property GetItem_Expand_REL_OrderItems_ItemNumber, int public static readwrite property GetItem_Expand_All_ItemNumber, int public static readwrite property GetItem_ByAltKey_VendorNumber_VendorNumber, int public static readwrite property GetItem_ByAltKey_Color_FlowerColor, String public static readwrite property GetItem_ByAltKey_Size_Size, int public static readwrite property GetItem_ByAltKey_Name_CommonName, String public static readwrite property UpdateItem_ItemNumber, int ;;------------------------------------------------------------ ;;Test data for Order ;; public static readwrite property GetOrders_Count, int public static readwrite property GetOrder_OrderNumber, int public static readwrite property GetOrder_Expand_REL_OrderItems_OrderNumber, int public static readwrite property GetOrder_Expand_REL_Customer_OrderNumber, int public static readwrite property GetOrder_Expand_All_OrderNumber, int public static readwrite property GetOrder_ByAltKey_CustomerNumber_CustomerNumber, int public static readwrite property GetOrder_ByAltKey_DateOrdered_DateOrdered, DateTime public static readwrite property GetOrder_ByAltKey_DateCompleted_DateCompleted, DateTime public static readwrite property UpdateOrder_OrderNumber, int ;;------------------------------------------------------------ ;;Test data for OrderItem ;; public static readwrite property GetOrderItems_Count, int public static readwrite property GetOrderItem_OrderNumber, int public static readwrite property GetOrderItem_ItemNumber, int public static readwrite property GetOrderItem_Expand_REL_Order_OrderNumber, int public static readwrite property GetOrderItem_Expand_REL_Order_ItemNumber, int public static readwrite property GetOrderItem_Expand_REL_Item_OrderNumber, int public static readwrite property GetOrderItem_Expand_REL_Item_ItemNumber, int public static readwrite property GetOrderItem_Expand_All_OrderNumber, int public static readwrite property GetOrderItem_Expand_All_ItemNumber, int public static readwrite property GetOrderItem_ByAltKey_ItemOrdered_ItemOrdered, int public static readwrite property GetOrderItem_ByAltKey_DateShipped_DateShipped, DateTime public static readwrite property GetOrderItem_ByAltKey_InvoiceNumber_InvoiceNumber, int public static readwrite property UpdateOrderItem_OrderNumber, int public static readwrite property UpdateOrderItem_ItemNumber, int ;;------------------------------------------------------------ ;;Test data for Vendor ;; public static readwrite property GetVendors_Count, int public static readwrite property GetVendor_VendorNumber, int public static readwrite property GetVendor_Expand_REL_Items_VendorNumber, int public static readwrite property GetVendor_Expand_All_VendorNumber, int public static readwrite property GetVendor_ByAltKey_State_State, String public static readwrite property GetVendor_ByAltKey_Zip_ZipCode, int public static readwrite property GetVendor_ByAltKey_PaymentTerms_PaymentTermsCode, String public static readwrite property UpdateVendor_VendorNumber, int endclass endnamespace
-
TestConstants.Values.dbl
This is a partial class with the TestConstants.Properties file, and contains assignment statements to actually assign values for the various data entities to be referenced when running unit tests. The code in the file should look like this:
import Microsoft.VisualStudio.TestTools.UnitTesting import Newtonsoft.Json import System.Collections.Generic import System.Net.Http namespace Services.Test public static partial class TestConstants static method TestConstants proc ;;------------------------------------------------------------ ;;Test data for Customer ;; GetCustomers_Count = 0 GetCustomer_CustomerNumber = 0 GetCustomer_Expand_REL_Orders_CustomerNumber = 0 GetCustomer_Expand_REL_Item_CustomerNumber = 0 GetCustomer_Expand_All_CustomerNumber = 0 GetCustomer_ByAltKey_State_State = "" GetCustomer_ByAltKey_Zip_ZipCode = 0 GetCustomer_ByAltKey_PaymentTerms_PaymentTermsCode = "" UpdateCustomer_CustomerNumber = 0 ;;------------------------------------------------------------ ;;Test data for Item ;; GetItems_Count = 0 GetItem_ItemNumber = 0 GetItem_Expand_REL_Vendor_ItemNumber = 0 GetItem_Expand_REL_OrderItems_ItemNumber = 0 GetItem_Expand_All_ItemNumber = 0 GetItem_ByAltKey_VendorNumber_VendorNumber = 0 GetItem_ByAltKey_Color_FlowerColor = "" GetItem_ByAltKey_Size_Size = 0 GetItem_ByAltKey_Name_CommonName = "" UpdateItem_ItemNumber = 0 ;;------------------------------------------------------------ ;;Test data for Order ;; GetOrders_Count = 0 GetOrder_OrderNumber = 0 GetOrder_Expand_REL_OrderItems_OrderNumber = 0 GetOrder_Expand_REL_Customer_OrderNumber = 0 GetOrder_Expand_All_OrderNumber = 0 GetOrder_ByAltKey_CustomerNumber_CustomerNumber = 0 GetOrder_ByAltKey_DateOrdered_DateOrdered = new DateTime() GetOrder_ByAltKey_DateCompleted_DateCompleted = new DateTime() UpdateOrder_OrderNumber = 0 ;;------------------------------------------------------------ ;;Test data for OrderItem ;; GetOrderItems_Count = 0 GetOrderItem_OrderNumber = 0 GetOrderItem_ItemNumber = 0 GetOrderItem_Expand_REL_Order_OrderNumber = 0 GetOrderItem_Expand_REL_Order_ItemNumber = 0 GetOrderItem_Expand_REL_Item_OrderNumber = 0 GetOrderItem_Expand_REL_Item_ItemNumber = 0 GetOrderItem_Expand_All_OrderNumber = 0 GetOrderItem_Expand_All_ItemNumber = 0 GetOrderItem_ByAltKey_ItemOrdered_ItemOrdered = 0 GetOrderItem_ByAltKey_DateShipped_DateShipped = new DateTime() GetOrderItem_ByAltKey_InvoiceNumber_InvoiceNumber = 0 UpdateOrderItem_OrderNumber = 0 UpdateOrderItem_ItemNumber = 0 ;;------------------------------------------------------------ ;;Test data for Vendor ;; GetVendors_Count = 0 GetVendor_VendorNumber = 0 GetVendor_Expand_REL_Items_VendorNumber = 0 GetVendor_Expand_All_VendorNumber = 0 GetVendor_ByAltKey_State_State = "" GetVendor_ByAltKey_Zip_ZipCode = 0 GetVendor_ByAltKey_PaymentTerms_PaymentTermsCode = "" UpdateVendor_VendorNumber = 0 endmethod endclass endnamespace
The code in this file is only generated one time, and not then overwritten. The intention is that you edit this file and provide real values for the collection counts and keys to be used for each of the unit test methods.
-
-
UnitTestEnvironment.dbl
The code in this file is used to both establish a known consistent environment for the unit tests to run in at the beginning of each run, and also to tear down that environment after all tests have completed.
Similar to the main self-hosting program, what this code mainly does is create a fresh set of data files for each test run, and delete those files after all tests have completed.
-
A new folder named
DataGenerators
was created in theServices.Test
project folder, and several source files were generated into the folder:-
CustomerLoader.dbl
-
ItemLoader.dbl
-
OrderItemLoader.dbl
-
OrderLoader.dbl
-
VendorLoader.dbl
These data generator classes are used during the loading of data in the UnitTestEnvironment class described above.
-
-
A new folder named
Models
was created in theServices.Test
project folder, and several source files were generated into the folder:-
Customer.dbl
-
Item.dbl
-
Order.dbl
-
OrderItem.dbl
-
Vendor.dbl
These data model classes are simplified versions of the data model classes that are generated into the
Services.Models
project, they are used to define and represent data entities on theclient
side, as the various unit tests are essentially HTTP clients to your Harmony Core web services.
-
-
A new folder named
UntTests
was created in theServices.Test
project folder, and several source files were generated into the folder:-
CustomerTests.dbl
-
ItemTests.dbl
-
OrderItemTests.dbl
-
OrderTests.dbl
-
VendorTests.dbl
These classes are special unit test classes, and there is a unit test method generated to test each of the operational endpoints on your generated OData controllers. Each of the test methods is coded to perform a specific function, based on the key values that are defined in the
TestConstants.Values.dbl
file. For example, theGet a single customer
test is configured to retrieve a specific customer, by primary key, and to verify that the correct entity is returned.
The test methods each use an
HttpClient
object, so they interact with your Harmony Core service the exact same way that any HTTP client would. -
Your next task is to add all of these new files to your Services.Test
project:
-
Right-click on the
Services.Test
project, selectAdd > Existing Item…
, then select all of the.dbl
files and click theAdd
button. -
Right-click on the
Services.Test
project, selectAdd > Existing Item…
, then drill into theDataGenerators
folder, select all of the.dbl
files and click theAdd
button. -
Repeat the process for the
Models
folder. -
Repeat the process for the
UnitTests
folder.
-
Edit
TestConstants.Values.dbl
-
Replace the assignment statements for
Test data for Customer
with the following code:GetCustomer_CustomerNumber = 1 GetCustomer_Expand_REL_Orders_CustomerNumber = 1 GetCustomer_Expand_REL_Item_CustomerNumber = 1 GetCustomer_Expand_All_CustomerNumber = 1 GetCustomer_ByAltKey_State_State = "CA" GetCustomer_ByAltKey_Zip_ZipCode = 94806 GetCustomer_ByAltKey_PaymentTerms_PaymentTermsCode = "01" UpdateCustomer_CustomerNumber = 3
-
Replace the assignment statements for
Test data for Item
with the following code:GetItem_ItemNumber = 1 GetItem_Expand_REL_Vendor_ItemNumber = 1 GetItem_Expand_REL_OrderItems_ItemNumber = 6 GetItem_Expand_All_ItemNumber = 6 GetItem_ByAltKey_VendorNumber_VendorNumber = 38 GetItem_ByAltKey_Color_FlowerColor = "white" GetItem_ByAltKey_Size_Size = 10 GetItem_ByAltKey_Name_CommonName = "Paper Mulberry" UpdateItem_ItemNumber = 22
-
Replace the assignment statements for
Test data for Order
with the following code:GetOrder_OrderNumber = 3 GetOrder_Expand_REL_OrderItems_OrderNumber = 3 GetOrder_Expand_REL_Customer_OrderNumber = 3 GetOrder_Expand_All_OrderNumber = 3 GetOrder_ByAltKey_CustomerNumber_CustomerNumber = 1 GetOrder_ByAltKey_DateOrdered_DateOrdered = new DateTime(2018,03,07) GetOrder_ByAltKey_DateCompleted_DateCompleted = new DateTime(2018,08,21) UpdateOrder_OrderNumber = 10
-
Replace the assignment statements for
Test data for OrderItem
with the following code:GetOrderItem_OrderNumber = 3 GetOrderItem_ItemNumber = 1 GetOrderItem_Expand_REL_Order_OrderNumber = 3 GetOrderItem_Expand_REL_Order_ItemNumber = 1 GetOrderItem_Expand_REL_Item_OrderNumber = 3 GetOrderItem_Expand_REL_Item_ItemNumber = 1 GetOrderItem_Expand_All_OrderNumber = 3 GetOrderItem_Expand_All_ItemNumber = 1 GetOrderItem_ByAltKey_ItemOrdered_ItemOrdered = 6 GetOrderItem_ByAltKey_DateShipped_DateShipped = new DateTime(2018,08,21) GetOrderItem_ByAltKey_InvoiceNumber_InvoiceNumber = 930301 UpdateOrderItem_OrderNumber = 999999 UpdateOrderItem_ItemNumber = 20
-
Replace the assignment statements for
Test data for Vendor
with the following code:GetVendor_VendorNumber = 38 GetVendor_Expand_REL_Items_VendorNumber = 40 GetVendor_Expand_All_VendorNumber = 40 GetVendor_ByAltKey_State_State = "MA" GetVendor_ByAltKey_Zip_ZipCode = 01000 GetVendor_ByAltKey_PaymentTerms_PaymentTermsCode = "" UpdateVendor_VendorNumber = 39
-
Save changes to and close the source file.
Before we move on, let's make sure the project builds:
-
Right-click on the
Services.Test
project and selectBuild
. -
Check the
Output
window and verify that the build was successful.1>------ Build started: Project: Services.Test, Configuration: Debug Any CPU ------ ========== Build: 1 succeeded, 0 failed, 5 up-to-date, 0 skipped ==========
-
Switch back to your previously used command prompt window
-
Move to into the
Services.Test
folder and run the unit tests via adotnet test
command:cd Services.Test dotnet test
You should see output that looks something like this:
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
Test Run Successful.
Total tests: 59
Passed: 59
Total time: 30.8590 Seconds
-
Tutorial 2: Building a Service from Scratch
- Creating a Basic Solution
- Enabling OData Support
- Configuring Self Hosting
- Entity Collection Endpoints
- API Documentation
- Single Entity Endpoints
- OData Query Support
- Alternate Key Endpoints
- Expanding Relations
- Postman Tests
- Supporting CRUD Operations
- Adding a Primary Key Factory
- Adding Create Endpoints
- Adding Upsert Endpoints
- Adding Patch Endpoints
- Adding Delete Endpoints
-
Harmony Core Code Generator
-
OData Aware Tools
-
Advanced Topics
- CLI Tool Customization
- Adapters
- API Versioning
- Authentication
- Authorization
- Collection Counts
- Customization File
- Custom Field Types
- Custom File Specs
- Custom Properties
- Customizing Generated Code
- Deploying to Linux
- Dynamic Call Protocol
- Environment Variables
- Field Security
- File I/O
- Improving AppSettings Processing
- Logging
- Optimistic Concurrency
- Multi-Tenancy
- Publishing in IIS
- Repeatable Unit Tests
- Stored Procedure Routing
- Suppressing OData Metadata
- Traditional Bridge
- Unit Testing
- EF Core Optimization
- Updating a Harmony Core Solution
- Updating to 3.1.90
- Creating a new Release
-
Background Information