(CORE) CORE (2022)

My TDD Technique for Backend API development with Xunit (Custom Attribute, WebClient GET/POST, JWT auth, Fact, Theory, InlineData, ClassData iterator function, Inject Log, Txt parsers for console output)

I have a couple articles about TDD, for example

this is continue about my way to development huge project step-by-step.

1. Project State.

This is my technology for support development process. Firstly, I have created this attribute


   1:  <AttributeUsage(AttributeTargets.Method)>
   2:  Public Class StateAttribute
   3:      Inherits Attribute
   4:   
   5:      Public ReadOnly Value
   6:      Public Sub New(value As String)
   7:          Me.Value = value
   8:      End Sub
   9:   
  10:  End Class

And than I have mark each API one-by-one on the way of development process.



Than I have created this simplest test.


   1:  Imports System.Reflection
   2:  Imports Xunit
   3:  Imports Xunit.Abstractions
   4:   
   5:  Public Class ProjectState
   6:      Private ReadOnly Log As ITestOutputHelper
   7:   
   8:      Public Sub New(_Log As ITestOutputHelper)
   9:          Log = _Log
  10:      End Sub
  11:   
  12:      <Fact>
  13:      Sub GetApiState()
  14:          Dim AllApi As Integer, OkApi As Integer, NfApi As Integer
  15:          Dim API As Assembly = Assembly.LoadFile("G:\Projects\CryptoChestMax\BackendAPI\BackendAPI\bin\Debug\net6.0\BackendAPI.dll")
  16:          Dim Types = API.GetTypes()
  17:          Dim Controllers As List(Of Type) = Types.Where(Function(X) X.Name.Contains("Controller")).ToList
  18:          For i As Integer = 0 To Controllers.Count - 1
  19:              Log.WriteLine($"{i + 1}. {Controllers(i).Name.Replace("Controller", "")}")
  20:              Dim Methods As List(Of MethodInfo) = DirectCast(Controllers(i), System.Reflection.TypeInfo).DeclaredMethods.ToList
  21:              For j As Integer = 0 To Methods.Count - 1
  22:                  Dim Attributes As List(Of CustomAttributeData) = Methods(j).CustomAttributes.ToList
  23:                  For k = 0 To Attributes.Count - 1
  24:                      If Attributes(k).AttributeType.Name = "StateAttribute" Then
  25:                          Dim Val As String = Attributes(k).ToString.Replace("[BackendAPI.StateAttribute(", "").Replace(")]", "")
  26:                          Log.WriteLine($"        {j + 1}. {Methods(j).Name} : {Val}")
  27:                          AllApi += 1
  28:                          If Val.ToLower.Contains("ok.") Then
  29:                              OkApi += 1
  30:                          ElseIf Val.ToLower.Contains("api not found") Then
  31:                              NfApi += 1
  32:                          End If
  33:                      End If
  34:                  Next
  35:              Next
  36:          Next
  37:          Log.WriteLine($"Summary: Total {AllApi}, Finished {OkApi}, NotFound {NfApi}.")
  38:      End Sub
  39:  End Class

And receive this result.



2. Authentication JWT token.

Usually Backend API need authorization, therefore first point is receive correct JWT token to perform API. This is my way to make this task .



   1:  Imports System.Net
   2:   
   3:  Public Class MyWebClient
   4:      Inherits WebClient
   5:      Protected Overloads Function GetWebRequest(URL As Uri) As WebRequest
   6:          Dim WebRequest = MyBase.GetWebRequest(URL)
   7:          WebRequest.ContentType = "application/json"
   8:          WebRequest.Timeout = Integer.MaxValue
   9:          Return WebRequest
  10:      End Function
  11:  End Class

   1:  Imports System.Net
   2:  Imports System.Net.Http
   3:  Imports System.Text
   4:  Imports BackendAPI
   5:  Imports Newtonsoft.Json
   6:   
   7:  Module Common
   8:      Public Function GetAdminToken(Request As MyWebClient) As String
   9:          Dim PostPrm = New BackendAPI.Model.AuthenticateRequest With {
  10:              .Username = "XXXXX",
  11:              .Password = "YYYYYYYYYYYYYYYYY"
  12:              }
  13:          Dim PostData = JsonConvert.SerializeObject(PostPrm)
  14:          Dim Response = Encoding.UTF8.GetString(Request.UploadData("/Users/Authenticate", Encoding.UTF8.GetBytes(PostData)))
  15:          Dim Ret1 = JsonConvert.DeserializeObject(Response)
  16:          Return Ret1("token").ToString
  17:      End Function
  18:   
  19:      Public Function GetUserToken(Request As MyWebClient, Username As String, Password As String) As String
  20:          Dim PostPrm = New BackendAPI.Model.AuthenticateRequest With {
  21:              .Username = Username,
  22:              .Password = Password
  23:              }
  24:          Dim PostData = JsonConvert.SerializeObject(PostPrm)
  25:          Dim Response = Encoding.UTF8.GetString(Request.UploadData("/Users/Authenticate", Encoding.UTF8.GetBytes(PostData)))
  26:          Dim Ret1 = JsonConvert.DeserializeObject(Response)
  27:          Return Ret1("token").ToString
  28:      End Function
  29:   
  30:  End Module

3. Inject Log.

Log injected to Xunit as any other NET CORE Module.


   1:  Public Class Container
   2:      Friend ReadOnly BaseUrl As String = "http://localhost:4000/"
   3:      Friend ReadOnly Request As MyWebClient
   4:      Private ReadOnly Log As ITestOutputHelper
   5:   
   6:      Public Sub New(_Log As ITestOutputHelper)
   7:          Request = New MyWebClient
   8:          Request.BaseAddress = BaseUrl
   9:          Request.Headers.Add("Content-Type", "application/json")
  10:          Log = _Log
  11:      End Sub

4. My technique to testing GET/POST API.

Standard testing technique mean usind WebApplicaionFactory.



Its possible, but not best way, this is not full complex technique to examine application, therefore I use little bit another way. This is my Technique to testing API.


   1:      <Fact>
   2:      Sub GetMasternodeInfo()
   3:          Dim Token = GetAdminToken(Request)
   4:          Request.Headers.Add("Content-Type", "application/json")
   5:          Request.Headers.Add("Authorization", "Bearer: " & Token)
   6:          Try
   7:              Dim Response = Request.DownloadString("/DbMasternode/GetMasternodeInfo")
   8:              Log.WriteLine(Response)
   9:          Catch ex As WebException
  10:              Dim Resp As String = ""
  11:              Dim Stream = ex.Response?.GetResponseStream()
  12:              If Stream IsNot Nothing Then
  13:                  Dim Sr = New IO.StreamReader(Stream)
  14:                  Resp = Sr.ReadToEnd
  15:              End If
  16:              Log.WriteLine(Resp & vbCrLf & ex.Message)
  17:          End Try
  18:      End Sub

You can also skip some test.



And this is template to perform POST.


   1:      <Fact>
   2:      <InlineData(1, 1, 1, "/root/.xsncore/xsn.tst", "/root/.xsncore/xsn-cli getstakingstatus", "tst", "............")>
   3:      Sub AddMasternodeInfo(toVm As Integer, toUser As Integer, toCoin As Integer, ConfigPath As String, StatusCommand As String, Comment As String, Config As String)
   4:          Dim Token = GetAdminToken(Request)
   5:          Request.Headers.Add("Content-Type", "application/json")
   6:          Request.Headers.Add("Authorization", "Bearer: " & Token)
   7:          Dim PostPrm = New SetUpMasterNodeRequest With {
   8:                          .toVm = toVm,
   9:                          .toUser = toUser,
  10:                          .toCoin = toCoin,
  11:                          .ConfigPath = ConfigPath,
  12:                          .StatusCommand = StatusCommand,
  13:                          .Comment = Comment
  14:                          }
  15:          Dim JsonSerializer = New JsonSerializer()
  16:          Dim PostData = JsonConvert.SerializeObject(PostPrm)
  17:          Try
  18:              Dim Response = Encoding.UTF8.GetString(Request.UploadData("/DbMasternode/AddMasternodeInfo", Encoding.UTF8.GetBytes(PostData)))
  19:              Log.WriteLine(Response)
  20:          Catch ex As WebException
  21:              Dim Resp As String = ""
  22:              Dim Stream = ex.Response?.GetResponseStream()
  23:              If Stream IsNot Nothing Then
  24:                  Dim Sr = New IO.StreamReader(Stream)
  25:                  Resp = Sr.ReadToEnd
  26:              End If
  27:              Log.WriteLine(Resp & vbCrLf & ex.Message)
  28:          End Try
  29:      End Sub

On the test above you can see how to pass test parameters with Fact and InlineData.


5. Repeat test for various data - manually and InlineData with iterator function.

You can repeat test with various data manually

And generate serial parameters with ClassData Iterator function.



   1:  Public Class DeleteVolumeParm
   2:      Implements IEnumerable(Of Object())
   3:   
   4:      Public Iterator Function GetEnumerator() As IEnumerator(Of Object()) Implements IEnumerable(Of Object()).GetEnumerator
   5:          For I As Integer = 1 To 11
   6:              Yield New Object() {"/dev/sdb" & I, True}
   7:          Next
   8:          For I As Integer = 1 To 6
   9:              Yield New Object() {"/dev/sdc" & I, True}
  10:          Next
  11:          For I As Integer = 8 To 11
  12:              Yield New Object() {"/dev/sdc" & I, True}
  13:          Next
  14:          For I As Integer = 1 To 6
  15:              Yield New Object() {"/dev/sdd" & I, True}
  16:          Next
  17:          Yield New Object() {"/dev/sdd9", True}
  18:      End Function
  19:   
  20:      Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
  21:          Return GetEnumerator()
  22:      End Function
  23:  End Class
  24:   
  25:      <Theory>
  26:      <ClassData(GetType(DeleteVolumeParm))>
  27:      Sub DeleteVolumeFromDB(Path As String, WithKvmDevicePartition As Boolean)
  28:      ....

6. Txt parsers debug.

Also I use test project to test various text parser and than insert to main project only correct program code. This is example.



7. Debug test.

If you want force start debugger (even test working in run mode) you can add this line.


System.Diagnostics.Debugger.Launch()

8. Update testing GET/POST API with Generic function.


This updating allow me extremely simplify testing code. And my testing code looks as:



So, this is my testing function:



   1:  Imports BackendAPI
   2:  Imports Newtonsoft.Json
   3:  Imports System.Net
   4:  Imports System.Runtime.CompilerServices
   5:  Imports System.Text
   6:  Imports Xunit.Abstractions
   7:   
   8:  Module RequestExtension
   9:      <Extension>
  10:      Public Sub PostRequest(Of T)(Request As MyWebClient, Log As ITestOutputHelper, Token As String, URL As String, PostPrm As T)
  11:          Request.Headers.Add("Content-Type", "application/json")
  12:          Request.Headers.Add("Authorization", "Bearer: " & Token)
  13:          Dim JsonSerializer = New JsonSerializer()
  14:          Dim PostData = JsonConvert.SerializeObject(PostPrm)
  15:          Try
  16:              Dim Response = Encoding.UTF8.GetString(Request.UploadData(URL, Encoding.UTF8.GetBytes(PostData)))
  17:              Log.WriteLine(Response)
  18:              Dim TmpDir As String = System.IO.Path.GetTempPath()
  19:              Dim TmpFile As String = IO.Path.Combine(TmpDir, Replace(Replace(Replace(URL, "/", "-"), "&", "-"), "?", "-"))
  20:              IO.File.WriteAllText(TmpFile & ".json", Response)
  21:              Dim Ret1 = Process.Start("C:\Program Files\Mozilla Firefox\firefox.exe", TmpFile & ".json")
  22:          Catch ex As WebException
  23:              Dim Resp As String = ""
  24:              Dim Stream = ex.Response?.GetResponseStream()
  25:              If Stream IsNot Nothing Then
  26:                  Dim Sr = New IO.StreamReader(Stream)
  27:                  Resp = Sr.ReadToEnd
  28:              End If
  29:              Log.WriteLine(Resp & vbCrLf & ex.Message)
  30:          End Try
  31:      End Sub
  32:   
  33:      <Extension>
  34:      Public Sub GetRequest(Request As MyWebClient, Log As ITestOutputHelper, Token As String, URL As String)
  35:          Request.Headers.Add("Content-Type", "application/json")
  36:          Request.Headers.Add("Authorization", "Bearer: " & Token)
  37:          Try
  38:              Dim Response = Request.DownloadString(URL)
  39:              Log.WriteLine(Response)
  40:              Dim TmpDir As String = System.IO.Path.GetTempPath()
  41:              Dim TmpFile As String = IO.Path.Combine(TmpDir, Replace(Replace(Replace(URL, "/", "-"), "&", "-"), "?", "-"))
  42:              IO.File.WriteAllText(TmpFile & ".json", Response)
  43:              Dim Ret1 = Process.Start("C:\Program Files\Mozilla Firefox\firefox.exe", TmpFile & ".json")
  44:          Catch ex As WebException
  45:              Dim Resp As String = ""
  46:              Dim Stream = ex.Response?.GetResponseStream()
  47:              If Stream IsNot Nothing Then
  48:                  Dim Sr = New IO.StreamReader(Stream)
  49:                  Resp = Sr.ReadToEnd
  50:              End If
  51:              Log.WriteLine(Resp & vbCrLf & ex.Message)
  52:          End Try
  53:      End Sub
  54:   
  55:      <Extension>
  56:      Public Sub GetAnonRequest(Request As MyWebClient, Log As ITestOutputHelper, URL As String)
  57:          Try
  58:              Dim Response = Request.DownloadString(URL)
  59:              Log.WriteLine(Response)
  60:              Dim TmpDir As String = System.IO.Path.GetTempPath()
  61:              Dim TmpFile As String = IO.Path.Combine(TmpDir, Replace(Replace(Replace(URL, "/", "-"), "&", "-"), "?", "-"))
  62:              IO.File.WriteAllText(TmpFile & ".json", Response)
  63:              Dim Ret1 = Process.Start("C:\Program Files\Mozilla Firefox\firefox.exe", TmpFile & ".json")
  64:          Catch ex As WebException
  65:              Dim Resp As String = ""
  66:              Dim Stream = ex.Response?.GetResponseStream()
  67:              If Stream IsNot Nothing Then
  68:                  Dim Sr = New IO.StreamReader(Stream)
  69:                  Resp = Sr.ReadToEnd
  70:              End If
  71:              Log.WriteLine(Resp & vbCrLf & ex.Message)
  72:          End Try
  73:      End Sub
  74:  End Module

Additional advance that I can receive and analyze JSON result of each test function directly ion browser.



9. Update ProjectState. Arrange API to security layers.

Look to this page please Simplest way to create tracing and authorization layers in Backend API. Special attribute to arrange API for authorization layers..



Comments ( )
Link to this page: http://www.vb-net.com/TDD-Technique/Index.htm
< THANKS ME>