Here we are using the mocking framework that we developed (GitHub - GendronAC/InterSystems-UnitTest-Mocking: This project contains a mocking framework to use with InterSystems' Products written in ObjectScript

Have a look at the https://github.com/GendronAC/InterSystems-UnitTest-Mocking/blob/master/S... class. Instead of calling ..SendRequestAsync we do ..ensHost.SendRequestAsync(...) Doing so enables us to create Expectations (..Expect(..ensHost.SendRequestAsync(....

Here a code sample : 

Class Sample.Src.CExampleService Extends Ens.BusinessService
{

/// The type of adapter used to communicate with external systems
Parameter ADAPTER = "Ens.InboundAdapter";

Property TargetConfigName As %String(MAXLEN = 1000);

Parameter SETTINGS = "TargetConfigName:Basic:selector?multiSelect=0&context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId}";

// -- Injected dependencies for unit tests

Property ensService As Ens.BusinessService [ Private ];

/// initialize Business Host object
Method %OnNew(
	pConfigName As %String,
	ensService As Ens.BusinessService = {$This}) As %Status
{
   set ..ensService = ensService
   return ##super(pConfigName)
}

/// Override this method to process incoming data. Do not call SendRequestSync/Async() from outside this method (e.g. in a SOAP Service or a CSP page).
Method OnProcessInput(
	pInput As %RegisteredObject,
	Output pOutput As %RegisteredObject,
	ByRef pHint As %String) As %Status
{
   set output = ##class(Ens.StringContainer).%New("Blabla")

   return ..ensService.SendRequestAsync(..TargetConfigName, output)
}

}
Import Sample.Src

Class Sample.Test.CTestExampleService Extends Tests.Fw.CUnitTestBase
{

Property exampleService As CExampleService [ Private ];

Property ensService As Ens.BusinessService [ Private ];

ClassMethod RunTests()
{
   do ##super()
}

Method OnBeforeOneTest(testName As %String) As %Status
{
   set ..ensService = ..CreateMock()

   set ..exampleService = ##class(CExampleService).%New("Unit test", ..ensService)
   set ..exampleService.TargetConfigName = "Some test target"

   return ##super(testName)
}

// -- OnProcessInput tests --

Method TestOnProcessInput()
{
   do ..Expect(..ensService.SendRequestAsync("Some test target",
                                             ..NotNullObject(##class(Ens.StringContainer).%ClassName(1)))
               ).AndReturn($$$OK)

   do ..ReplayAllMocks()

   do $$$AssertStatusOK(..exampleService.OnProcessInput())

   do ..VerifyAllMocks()
}

Method TestOnProcessInputFailure()
{
   do ..Expect(..ensService.SendRequestAsync("Some test target",
                                             ..NotNullObject(##class(Ens.StringContainer).%ClassName(1)))
               ).AndReturn($$$ERROR($$$GeneralError, "Some error"))

   do ..ReplayAllMocks()

   do $$$AssertStatusNotOK(..exampleService.OnProcessInput())

   do ..VerifyAllMocks()
}

}

Here are a few code samples that could help : 

 

Class YourPackage.REST.CSoapAuthenticator Extends %CSP.REST
{

Parameter CONTENTTYPE = "text/xml";
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/:soapService" Method="POST" Call="PostSOAP"/>
</Routes>
}

ClassMethod PostSOAP(
    strSoapService As %String = "",
    test As %Integer = 0) As %Status
{
   #Dim %request As %CSP.Request
   #Dim %response As %CSP.Response
   #Dim httpClient As %Net.HttpRequest = ..GetLoopbackSOAPClient()
   do httpClient.EntityBody.Write(..UpdateSoapBody(%request.Content.Read($$$MaxStringLength)))
   do httpClient.SetHeader("SOAPAction", %request.SoapAction)
   do httpClient.Post($System.CSP.GetDefaultApp($Namespace) _ "/" _ strSoapService, test)

   set %response.ContentType = "text/xml"
   write httpClient.HttpResponse.Data.Read($$$MaxStringLength)

   return $$$OK
}

// -- Private utils --
ClassMethod GetLoopbackSOAPClient() As %Net.HttpRequest [ Private ]
{
   set httpClient = ##class(%Net.HttpRequest).%New()
   set httpClient.Server = ...
   set httpClient.Port = ...
   set httpClient.Timeout = 5
   set httpClient.Https = 1
   set httpClient.SSLConfiguration = ...
  
   return httpClient
}

ClassMethod UpdateSoapBody(strInput As %String) As %String [ Private ]
{
   #Dim strSoap As %String = ""
   #Dim username As %String = ... (Extract from Input)
   #Dim password As %String = ... (Extract from Input)
   
   set strSoap = strSoap _ ... (Copy soap enveloppe from input)
   set strSoap = strSoap _ " <soap:Header>" _ ..GenerateSecurityHeader(username, password) _ " </soap:Header>"
   set strSoap = strSoap _ " <soap:Body>"
   ... 
   set strSoap = strSoap _ " </soap:Body>"
   set strSoap = strSoap _ "</soap:Envelope>"
   return strSoap
}

ClassMethod GenerateSecurityHeader(
    strUsername As %String,
    strPassword As %String) As %String [ Private ]
{
   set header = ##class(%SOAP.Security.Header).%New()
   set usernameToken = ##class(%SOAP.Security.UsernameToken).Create(strUsername, strPassword)
   do header.AddSecurityElement(usernameToken)

   return ... (Use XML Writer to Output header to a String.)
}

}

I just remembered, I fixed this but was waiting for a review. I merged it this morning.

https://github.com/GendronAC/InterSystems-UnitTest-Mocking/pull/3

(code was sent to github per InterSystems request)

Latest code is here : 
https://github.com/GendronAC/InterSystems-UnitTest-Mocking

Let me know if you need something else. Have a look at the CTestCustomPassthroughOperation.cls class

Thank you, I will have a look at that and also, I'm replicating my setup on a redhat gitlab-runner. I'll update this post if I find my way out of this on Windows. I also noticed that the "ENVIRONMENT" variables were not passed appropriately in a way that csession understands. The  $system.Util.GetEnviron("CI_PROJECT_DIR") and  $system.Util.GetEnviron("CI_COMMIT_SHA") calls both returns an empty string. 

Perhaps the <NOTOPEN> is related to the way the stdout is read in windows. 

Hi Eduard,

I had a look at your continuous delivery articles and found them awesome! I tried to set up a similar environment but I'm struggling with a detail... Hope you'll be able to help me out.

I currently have a working gitlab-runner installed on my Windows Laptop with a working Ensemble 2018.1.1 with the isc.gitlab package you provided.

C:\Users\gena6950>csession ENS2K18 -U ENSCHSPROD1 "##class(isc.git.GitLab).test()"

===============================================================================
Directory: C:\Gitlab-Runner\builds\ijGUv41q\0\ciussse-drit-srd\ensemble-continuous-integration-tests\Src\ENSCHS1\Tests\Unit\
===============================================================================

[...]

Use the following URL to view the result:
http://10.225.31.79:8971/csp/sys/%25UnitTest.Portal.Indices.cls?Index=23&$NAMESPACE=ENSCHSPROD1
All PASSEDD
C:\Users\gena6950>

I had to manually "alter" the .yml file because of a new bug with parenthesis in the gitlab-runner shell commands (see https://gitlab.com/gitlab-org/gitlab-runner/issues/1941). Relevant parts of this file are there (the file itself is larger but I think it's irrelevant). I put a "Echo" there to see how the command was received by the runner.

stages:
  - load
  - test
  - package

variables:
  LOAD_DIFF_CMD: "##class(isc.git.GitLab).loadDiff()"
  TEST_CMD: "##class(isc.git.GitLab).test()"
  PACKAGE_CMD: "##class(isc.git.GitLab).package()"

.script_test: &script_test
  stage: test
  script: 
  - echo csession ENS2K18 -U ENSCHSPROD1 "%TEST_CMD%"
  - csession ENS2K18 -U ENSCHSPROD1 "%TEST_CMD%"
  artifacts:
    paths: 
      - tests.html

And this is the output seen by the gitlab output : 

Running with gitlab-runner 11.7.0 (8bb608ff)
  on Laptop ACGendron ijGUv41q
Using Shell executor...
Running on CH05CHUSHDP1609...
Fetching changes...
Removing tests.html
HEAD is now at b1ef284 Ajout du fichier de config du pipeline Gitlab
Checking out b1ef284e as master...
Skipping Git submodules setup
$ echo csession ENS2K18 -U ENSCHSPROD1 "%TEST_CMD%"
csession ENS2K18 -U ENSCHSPROD1 "##class(isc.git.GitLab).test()"
$ csession ENS2K18 -U ENSCHSPROD1 "%TEST_CMD%"
<NOTOPEN>>ERROR: Job failed: exit status 1

I'm pretty sure it must be a small thing but I can't put my finger on it!

Hope you'll be able to help!

Kind regards

Andre-Claude