Unit Test Examples

Typical unit tests can be:

  • Element test result (what the element returns).

  • Function test – a testing function is defined inside of an element (Library or any other element).

How to Test Element Result

An element result test checks the result of the element itself. This is suitable for elements that return a value in a price list, LPG or any other logic.

For this example, let's have a simple element called “FinalPriceEvolPct”. It returns either evolution value in percent or null if conditions are not satisfied. Its result is dependent on the result of another element.

Element code:

if (out.IsResultElementNewPME) { def newPME = out.NewPME def basePME = out.BasePME if(newPME != null && basePME !=null && basePME !=0){ return (newPME / basePME) - 1 } } else if(out.IsResultElementNewListPrice){ def newListPrice = out.NewListPrice def baseListPrice = out.BaseListPrice if(newListPrice !=null && baseListPrice !=null && baseListPrice !=0){ return (newListPrice / baseListPrice) - 1 } } return null

 

The first test for this element is to check what happens if all other elements are null. The expected result is null. Other elements referred to in the code as “out.NewPME” are defined to be null in the function .withLogicTestDoubles.

def "FinalPriceEvolPct returns null if all inputs are null for NewPME result element"() { when: TestRun testRun = TestRun.builder() .withLogicTestDoubles("api" : [], out: [ "NewPME": null, "BasePME":null, "IsResultElementNewPME":true ]) .buildElementTest(LOGIC_DIR, ELEMENT_NAME) then: testRun.execute() .getElementTestResult() == null }

 

Now we can play with this element a little more – modify the input values NewPme, BasePME and IsResultElementNewPME and check the output value. If anyone changes this element in the future, we will identify it very quickly as the test will fail.

This sample shows this scenario: if NewPME is equal to 100, BasePME is equal to 20 and IsResultElementNewPME is equal to true, the result must be 4.

def "FinalPriceEvolPct returns calculated value for NewPME result element"() { when: TestRun testRun = TestRun.builder() .withLogicTestDoubles("api" : [], out: [ "NewPME": 100, "BasePME":20, "IsResultElementNewPME":true ]) .buildElementTest(LOGIC_DIR, ELEMENT_NAME) then: testRun.execute() .getElementTestResult() == 4 }

Now you can write the remaining tests for other combinations of input parameters:

  • What happens if NewPME is 100and BasePME 20?

  • What happens if BasePME is null and NewPME 100?

  • What happens if BasePME is -1000 and NewPME -200?

  • What happens if IsResultElementNewPME is null and NewPME is 100 and BasePME 20?

  • and so on.

 

In a situation where you change just the test input and the test result and the rest of the test are the same you can:

  • Write a standalone test for each combination of input and result.

  • Write a test with the where clause with a definition of the input output combination. See a sample:

def "FinalPriceEvolPct returns calculated value for NewPME result element"() { when: TestRun testRun = TestRun.builder() .withLogicTestDoubles("api": [], out: [ "NewPME" : NewPme, "BasePME" : BasePME, "IsResultElementNewPME": IsResultElementNewPME ]) .buildElementTest(LOGIC_DIR, ELEMENT_NAME) then: testRun.execute() .getElementTestResult() == expectedResult where: NewPme | BasePME | IsResultElementNewPME | expectedResult 100 | 20 | true | 4 null | 100 | true | null -1000 | -200 | true | 4 }

How to Test a Function

Inside of an element we can have a function definition that we want to test separately to see how the function behaves with different inputs.

Let’s have the following element called RetailerSIPrice (the code is not so relevant, so you do not need to study it in detail):

.. .. Some element code not important for this sample... .. def getRetailerSIPrice(retailerSIRecords) { retailerSIRecords = retailerSIRecords.findAll { it.RetailerSIPriceEUR != null } // Remove all null values in RetailerSIPriceEUR def retailerSIPrice if (retailerSIRecords) { retailerSIPrice = retailerSIRecords?.RetailerSIPriceEUR?.sum() / retailerSIRecords?.size() } else { retailerSIPrice = null } return retailerSIPrice }

The important part of this element is the function “getRetailerSIPrice” and this function can be tested standalone.

 

Comparing the two types of tests, the main difference is:

  • The element test uses the function .getElementTestResult().

  • The function test uses the function .getElementScript().getRetailerSIPrice to run just a method from the element itself.

So let’s prepare a basic test for this function. This test tests the function result if the input list is null.

def "getRetailerSIPrice returns null if retailerSIRecords is null"() { when: def retailerSIRecords = null TestRun testRun = TestRun.builder() .buildElementTest(LOGIC_DIR, ELEMENT_NAME) then: testRun.getElementScript().getRetailerSIPrice(retailerSIRecords) == null }

If the input list is null, the result of the function is null. This test typically discovers any unhandled null check.

Now let’s have non null input for the second test:

def "getRetailerSIPrice calculates only records with RetailerSIPriceEUR value"() { when: def retailerSIRecords = [["Country": "FRANCE", "RetailerSIPriceEUR": 10], ["Country": "FRANCE", "RetailerSIPriceEUR": 11], ["Country": "FRANCE", "RetailerSIPriceEUR": 12], ["Country": "FRANCE", "RetailerSIPriceEUR": 13], ["Country": "FRANCE", "MyPrice": 14]] TestRun testRun = TestRun.builder() .buildElementTest(LOGIC_DIR, ELEMENT_NAME) then: testRun.getElementScript().getRetailerSIPrice(retailerSIRecords) == 11.5 }

The function returns a calculated result for this sample == 11.5.

More Complex Test Definition

This section contains the following sample test definitions:

How to Mock API Function (findLookupTable or find etc.)

You can do it via the withLogicTestDouble function or as part of the withLogicTestDoubles function. See line 13 in this sample. The withLogicTestDoubles function simply contains an “api” map, with mocked API functions.

def "getRetailerSIRecords returns records as array of maps with values in big decimal"() { when: def countryArray = ["FRANCE", "ROMANIA"] def extTyreId = "515145" def startMonthExternalPricingSI = "201901" def endMonthExternalPricingSI = "202401" def globalTable = ["EU3CountryArray": [["attribute1": "FRANCE", "attribute11": "FRANCE",], ["attribute1": "ROMANIA", "attribute11": "ROMANIA"]]] TestRun testRun = TestRun.builder() .withLogicTestDoubles(["api": [ "global" : globalTable, "findLookupTable" : { String name -> return ["id": "10"] }, "getMaxFindResultsLimit": { -> return 200 }, "find" : { String typecode, Integer start, Integer end, String sortBy, def fieldsList, def filter1, def filter2, def filter3, def filter4, def filter5 -> return [ ["key1": "FRANCE", "attribute10": "64.6"], ["key1": "FRANCE", "attribute10": "64.6"], ["key1": "FRANCE", "attribute10": "64.6"], ["key1": "FRANCE", "attribute10": "64.6"], ["key1": "FRANCE", "attribute10": "64.6"], ["key1": "ROMANIA", "attribute10": "63.4620732"], ["key1": "ROMANIA", "attribute10": "63.4620732"] ] } ], "libs" : getSharedLibrary()]) .buildElementTest(LOGIC_DIR, ELEMENT_NAME) then: testRun.getElementScript().getRetailerSIRecords(countryArray, extTyreId, startMonthExternalPricingSI, endMonthExternalPricingSI) == [ [Country: "FRANCE", RetailerSIPriceEUR: 64.6], [Country: "FRANCE", RetailerSIPriceEUR: 64.6], [Country: "FRANCE", RetailerSIPriceEUR: 64.6], [Country: "FRANCE", RetailerSIPriceEUR: 64.6], [Country: "FRANCE", RetailerSIPriceEUR: 64.6], [Country: "ROMANIA", RetailerSIPriceEUR: 63.4620732], [Country: "ROMANIA", RetailerSIPriceEUR: 63.4620732]] }

How to Mock api.stream

Simply define api.stream the same way as any other API function and add .stream() at the end of the values list. For a sample see line 26.

def "getPXasMap return map with specified attribute as key"() { when: TestRun testRun = TestRun.builder() .withLogicTestDouble("api", [ "getMaxFindResultsLimit": { -> return 200 }, "stream" : { String typeCode, def orderBy, def fields, def filters -> return [ [ "attribute18": "*", "attribute17": "*", "attribute39": "*", "attribute37": "*" ], [ "attribute18": "SNOW", "attribute17": "RIKEN", "attribute39": "PC", "attribute37": "Winter" ], [ "attribute18": "ENERGY SAVER", "attribute17": "CompanyABC", "attribute39": "PC", "attribute37": "Summer" ] ].stream() } ]) .buildElementTest LOGIC_DIR, ELEMENT_NAME and: Script script = testRun.getElementScript() then: script.getPXasMap("CommercialCatalog", "attribute18", ["attribute18", "attribute17", "attribute37", "attribute39"]) == ["*" : ["attribute18": "*", "attribute17": "*", "attribute39": "*", "attribute37": "*"], "SNOW" : ["attribute18": "SNOW", "attribute17": "RIKEN", "attribute39": "PC", "attribute37": "Winter"], "ENERGY SAVER": ["attribute18": "ENERGY SAVER", "attribute17": "CompanyABC", "attribute39": "PC", "attribute37": "Summer"]] }

How to Mock Another Element Result

This is a very common case – elements or functions use another element result and you have to mock it in your test. You can do it with an “out” map like in this example, see line 5 and below.

def "NewListPricePack Element test - all data defined returns calculated value"() { when: TestRun testRun = TestRun.builder() .withLogicTestDoubles("out": [ "CurrentListPrice" : 10, "NewSOPricePack" : 20, "SOPriceActual" : 2, "IsResultElementNewListPrice":true ]) .buildElementTest(LOGIC_DIR, ELEMENT_NAME) and: Script script = testRun.getElementScript() then: testRun.execute() .getElementTestResult() == 100 }

How to Mock api.global or api.local Variables

This is necessary if an element or function you test uses a value from the api.global or api.local table. See this example, especially lines 8 and 12. This sample defines a global table; the api.local table is defined the same way, just change the keyword to “local”.

def "getRetailerSIRecords returns records as array of maps with values in big decimal"() { when: def countryArray = ["FRANCE", "ROMANIA"] def extTyreId = "515145" def startMonthExternalPricingSI = "201901" def endMonthExternalPricingSI = "202401" def globalTable = ["EU3CountryArray": [["attribute1": "FRANCE", "attribute11": "FRANCE",], ["attribute1": "ROMANIA", "attribute11": "ROMANIA"]]] TestRun testRun = TestRun.builder() .withLogicTestDoubles(["api": [ "global" : globalTable, "findLookupTable" : { String name -> return ["id": "10"] }, "getMaxFindResultsLimit": { -> return 200 }, "find" : { String typecode, Integer start, Integer end, String sortBy, def fieldsList, def filter1, def filter2, def filter3, def filter4, def filter5 -> return [ ["key1": "FRANCE", "attribute10": "64.6"], ["key1": "FRANCE", "attribute10": "64.6"], ["key1": "FRANCE", "attribute10": "64.6"], ["key1": "FRANCE", "attribute10": "64.6"], ["key1": "FRANCE", "attribute10": "64.6"], ["key1": "ROMANIA", "attribute10": "63.4620732"], ["key1": "ROMANIA", "attribute10": "63.4620732"] ] } ], "libs" : getSharedLibrary()]) .buildElementTest(LOGIC_DIR, ELEMENT_NAME) then: testRun.getElementScript().getRetailerSIRecords(countryArray, extTyreId, startMonthExternalPricingSI, endMonthExternalPricingSI) == [ [Country: "FRANCE", RetailerSIPriceEUR: 64.6], [Country: "FRANCE", RetailerSIPriceEUR: 64.6], [Country: "FRANCE", RetailerSIPriceEUR: 64.6], [Country: "FRANCE", RetailerSIPriceEUR: 64.6], [Country: "FRANCE", RetailerSIPriceEUR: 64.6], [Country: "ROMANIA", RetailerSIPriceEUR: 63.4620732], [Country: "ROMANIA", RetailerSIPriceEUR: 63.4620732]] }

How to Mock Function from (Shared) Library

Let’s say our function uses another function from Shared Library and we have to mock it. You can either mock it inside the test or mock it as a separate function. Here you can see how to do it: lines 1-10 and 29. The keyword you need is “libs”.

def getSharedLibrary(String val) { return [ "Library": [ "Conversions": [ "convertToBigDecimal": { value -> return value?.toBigDecimal() } ] ] ] } def "getRetailerSIRecords returns records as array of maps with values in big decimal"() { when: def countryArray = ["FRANCE", "ROMANIA"] def extTyreId = "515145" def startMonthExternalPricingSI = "201901" def endMonthExternalPricingSI = "202401" def globalTable = ["EU3CountryArray": [["attribute1": "FRANCE", "attribute11": "FRANCE",], ["attribute1": "ROMANIA", "attribute11": "ROMANIA"]]] TestRun testRun = TestRun.builder() .withLogicTestDoubles(["api": [ "global" : globalTable, "findLookupTable" : { String name -> return ["id": "10"] }, "getMaxFindResultsLimit": { -> return 200 }, "find" : { String typecode, Integer start, Integer end, String sortBy, def fieldsList, def filter1, def filter2, def filter3, def filter4, def filter5 -> return [["key1": "FRANCE", "attribute10": "64.6"]] } ], "libs" : getSharedLibrary()]) .buildElementTest(LOGIC_DIR, ELEMENT_NAME) then: testRun.getElementScript().getRetailerSIRecords(countryArray, extTyreId, startMonthExternalPricingSI, endMonthExternalPricingSI) == [ [Country: "FRANCE", RetailerSIPriceEUR: 64.6]] }

How to Test Function if It Modifies Local or Global Table and Always Returns null

Let’s have an element that modifies the local/global table and always returns null. The questions is: how to test such an element if the return value is always the same (null)?

Let’s have the element code that just adds a value to a local map. The most important lines are 26 where a map value is added and 29 where null is always returned.

if (out.EU3LPG) { for (eu3CountryRecord in api.global.EU3CountryArray) { def highLowRMLogic = null def EU3countryName = eu3CountryRecord.attribute1 def eu3countryRecord = api.local.countryMatrixMap.get(EU3countryName) def targetSIPriceLowRM = eu3countryRecord.get("TargetSIPriceLowRM") def targetSIPriceHighRM = eu3countryRecord.get("TargetSIPriceHighRM") def targetSIPriceHighMaxRM = eu3countryRecord.get("TargetSIPriceHighMaxRM") def targetSIPriceHighMinRM = eu3countryRecord.get("TargetSIPriceHighMinRM") if (targetSIPriceLowRM == null && targetSIPriceHighRM == null) { highLowRMLogic = null } else if (targetSIPriceLowRM == null) { highLowRMLogic = "Adjusted to High Comp Index Target" } else if (targetSIPriceHighMaxRM != null && targetSIPriceHighMinRM != null) { if (targetSIPriceHighMaxRM <= targetSIPriceLowRM && targetSIPriceLowRM <= targetSIPriceHighMinRM) { highLowRMLogic = "Adjusted to Low Comp Index Target" } else if (targetSIPriceLowRM > targetSIPriceHighMinRM) { highLowRMLogic = "Adjusted to High Min Comp Index Target" } else if (targetSIPriceLowRM < targetSIPriceHighMinRM) { highLowRMLogic = "Adjusted to High Max Comp Index Target" } } api.local.countryMatrixMap.get(EU3countryName).put("HighLowRMLogic", highLowRMLogic) } } return null

 

And now, the test for such an element can look like this:

def "HighLowRMLogic value stored in local map - TargetSIPriceLowRM is null "() { when: def globalTable = ["EU3CountryArray": [["attribute1": "FRANCE", "attribute11": "FRANCE",], ["attribute1": "ROMANIA", "attribute11": "ROMANIA"]]] def localTable = ["countryMatrixMap": ["FRANCE" : ["TargetSIPriceLowRM": null, "TargetSIPriceHighRM": 10], "ROMANIA": [:]]] TestRun testRun = TestRun.builder() .withLogicTestDoubles( "out": [ "EU3LPG": true ], "api": ["global": globalTable, "local" : localTable] ) .buildElementTest(LOGIC_DIR, ELEMENT_NAME) and: Script script = testRun.getElementScript() then: testRun.execute().getElementTestResult() == null localTable == ["countryMatrixMap": ["FRANCE": ["TargetSIPriceLowRM": null, "TargetSIPriceHighRM": 10, "HighLowRMLogic": "Adjusted to High Comp Index Target"], "ROMANIA":["HighLowRMLogic":null]]]

The most important lines are:

  • 5 where the input value of the local table is defined.

  • 23 where we check if the return value from elements is null.

  • 24 where we check if the value in the local map has been changed.

How to Test Element/Function Using Function from Another Element

Let’s have a (simplified) element using a function from another element (see line 3, element Library in the same logic).

if ((out.PricingStrategyProposed || adjustmentFactorVs == "current") && out.IsResultElementNewListPrice) { def newListPrice = (listPrice * (1 + adjustmentFactorPct)) + adjustmentFactorUnit def newListPriceAfterThresholdsRounded = Library.applyThresholdOnResultPriceWithRounding(newListPrice, out.BaseListPrice, out.IncreaseThresholdPct, out.DecreaseThresholdPct) return newListPriceAfterThresholdsRounded }

This is possible if you have Groovy in version 2.4.12 and lower, but not possible if you have a higher version. You cannot mock the function via withLogicTestDoubles and the function withAdditionalElementInitialized does not work properly. See the related issue for a test sample and result.

Found an issue in documentation? Write to us.