Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • The use of reusable functions allows for test features with less code duplication and improved readability.

  • Ignore the output by prepending the reusable function call with def v = call ...

...

titleReusable functions: thunderjet/cross-modules/features/cancel-invoice-with-encumbrance.feature

...

  • Nearly all function calls must be prepended with def v = call ..., particularly if the output of the function is not required in further assertion or data preparation. In some cases, such as global variable initialization, user/admin login and others, the output must not be ignored, hence def v = call ... prefix should not be used.

Expand
titleReusable functions: thunderjet/cross-modules/features/cancel-invoice-with-encumbrance.feature
Code Block
# MODINVOICE-544
Feature: Cancel an invoice with an Encumbrance

  Background:
    * url baseUrl

    ############################################
    # NOTICE the missing prefix def v = call ...
    # Output of this call is reassigned on the next line
    * call login testAdmin
    * def okapitokenAdmin = okapitoken

    ############################################
    # NOTICE the missing prefix def v = call ...
    # Output of this call is reassigned on the next line
    * call login testUser
    * def okapitokenUser = okapitoken

    * def headersUser = { 'Content-Type': 'application/json', 'x-okapi-token': '#(okapitokenUser)', 'Accept': 'application/json' }
    * def headersAdmin = { 'Content-Type': 'application/json', 'x-okapi-token': '#(okapitokenAdmin)', 'Accept': 'application/json' }

    * configure headers = headersUser
    
    ############################################
    # NOTICE the missing prefix def v = call ...
    # Output of this call is reused globally
    * callonce variables
    
    * def createOrder = read('classpath:thunderjet/mod-orders/reusable/create-order.feature')
    * def openOrder = read('classpath:thunderjet/mod-orders/reusable/open-order.feature')
    * def createInvoice = read('classpath:thunderjet/mod-invoice/reusable/create-invoice.feature')
    * def createInvoiceLine = read('classpath:thunderjet/mod-invoice/reusable/create-invoice-line.feature')
    * def approveInvoice = read('classpath:thunderjet/mod-invoice/reusable/approve-invoice.feature')
    * def payInvoice = read('classpath:thunderjet/mod-invoice/reusable/pay-invoice.feature')
    * def cancelInvoice = read('classpath:thunderjet/mod-invoice/reusable/cancel-invoice.feature')
    
    @Positive
    Scenario: Cancel an invoice with an Encumbrance from a PO line in "Pending" payment status
      * print "1. Create finances"
      ...
    
      * print "2. Create an order and line"
      ...
      
      * print "3. Open the order"
      * def v = call openOrder { orderId: "#(orderId)" }
  
      * print "4. Create an invoice"
      * def v = call createInvoice { id: "#(invoiceId)" }
  
      * print "5. Get the encumbrance id"
      Given path 'orders/order-lines', poLineId
      When method GET
      Then status 200
      * def poLine = $
      * def encumbranceId = poLine.fundDistribution[0].encumbrance
  
      * print "6. Add an invoice line linked to the po line"
      * def v = call createInvoiceLine { invoiceLineId: "#(invoiceLineId)", invoiceId: "#(invoiceId)", poLineId: "#(poLineId)", fundId: "#(fundId)", encumbranceId: "#(encumbranceId)", total: 1 }
  
      * print "7. Approve the invoice"
      * def v = call approveInvoice { invoiceId: "#(invoiceId)" }
  
      * print "8. Pay the invoice"
      * def v = call payInvoice { invoiceId: '#(invoiceId)' }
  
      * print "9. Cancel the invoice"
      * def v = call cancelInvoice { invoiceId: '#(invoiceId)' }
  
      * print "10. Check the encumbrance"
      Given path 'finance/transactions'
      And param query = 'id==' + encumbranceId
      When method GET
      Then status 200
      * def transaction = $
      * print 'Encumbrance transaction: ', transaction
      And match $.transactions[0].encumbrance.status == 'Unreleased'

...

  • When asserting a response that has a possibility of delay, add a retry rather than "pausing" the test execution

  • Utilize the contains assertion to check if a collection contains specific elements or values.

  • Combine assertions with the match keyword to perform deep equality checks on collections and allow flexibility for objects when additional properties are added.

    Code Block
    // Bad, if one more property is added to the progress object by the system under test,
    //  the assertion will fail
    And match response.jobExecutions[0].progress == {exported:1, failed:{duplicatedSrs:0,otherFailed:0}, total:1}
    
    // Good
    And match response.jobExecutions[0].progress contains {exported:1, failed:0, duplicatedSrs:0, total:1}
  • Use the contains any or contains only and other assertions like these to check for the presence or absence of specific elements in a collection. Never assume that the order of objects in a collection is going to be guaranteed.

    Code Block
    // Bad
    And match $.requests[0].item.callNumberComponents.callNumber == callNumber1
    And match $.requests[1].item.callNumberComponents.callNumber == callNumber2
    
    // Good
    And match response.requests[*].item.callNumberComponents.callNumber contains callNumber1
    And match response.requests[*].item.callNumberComponents.callNumber contains callNumber2

...