CASE: liquid testing - UK working papers - Payroll reconciliation BS

Eager to get started with liquid testing? Below you can find a case on how we implemented the liquid testing YAML code for the Payroll reconciliation BS template in the UK working papers package, as well as how we interpreted our liquid testing guidelines.

GUIDELINES IMPLEMENTATION

Coding best practices

  • Clearly separated parts with comments
  • Add a one-line comment before each test to explain in human language what this test is about
  • Add a numbering for the unit + test in each comment explaining the test
  • Test top-level key describes what this test is about
  • Remember to use clear indentation and spacing

See example of test naming and header below:

 # fifth test (tax liability calculation)
 tax_liability:

Make use of debug mode

  • All results & text properties (= values inside customs) were copied over from the debug mode. This way we’re immediately sure the values added in the test are the same as when the template is completed on the platform.
  • The data in the debug is shown in JSON format, but the liquid testing needs to be made in YAML. Values from the ‘Named results’, ‘TextProperties’ and ‘Rollforward parameters’ sections in the debug will need to be included in our testing.

See below how ‘Named results’ appears in debug mode:

{
  "average_monthly_employees": "0.083333333333333333",
  "default_selected_hmrcLiability": "240501,240507,250501,250507",
  "social_insurance_difference": null,
  "default_selected_wagesLiability": "240503,250503",
  "wages_difference": "20.0",
  "default_selected_pensionLiability": "240504,250504",
  "pension_difference": "23.0"
}

Start with an empty state

  • We’ve started with an empty state to set the global settings that are applicable for all units & tests (company / context information).
  • Usually there are results being created even if nothing is completed in the template, this way we also have an overview of which results are always there.
 # empty state
empty_state: 
  context:
    period: 2021-12-31
  data:
    periods:
      2021-12-31:
        reconciliations:
          newreconkalpna:
  expectation: 
    reconciled: true 
    results: 
      default_selected_hmrcLiability: "240501,240507,250501,250507"
      social_insurance_difference: 0 
      default_selected_wagesLiability: "240503,250503" 
      wages_difference: 0 
      default_selected_pensionLiability: "240504,250504" 
      pension_difference: 0 

Identifying units

  • Enter data in to your template and determine where else is affected by this data. I.e. whether the data flows through elsewhere in your template.
  • For example, the main payroll analysis table totals for national insurance will flow through into the national insurance reconciliation further down in the template. However, the average number of employees table would be classified as a separate unit to the above, as this data is not flowed through elsewhere.

Identifying tests

  • As a starting point it’s good to get to know how the template works in general, and where user data is inputted, including what effect that has on the rest of the template (i.e. whether this information feeds through into another calculation/template.)

    • Examples here are the net wage, tax liability, pension liability and average monthly employees calculations in my payroll analysis table and average employees table.
  • Values are added through a collection - fori

    • This means we’ll need to add custom values to both the accounts and the reconciliations blocks in the tests.
  • The template has reconciliation activated (green dot / red triangle), so we’ll need to write tests for both success (= reconciled) and error (= unreconciled) paths.

  • The template has the opportunity for the user accounts via the # functionality, we will need to consider if these are working as expected in the three various tables.

  • Account collections can also be configured and so this has been included in the liquid testing

  • Tests will be defined based on how the user could complete the template

  • No specific rollforward logic is present in the template, so we don’t need to consider any rollforwards in our expectation.

Derive tests based on the code

  • Explained above are tests which have been formulated by inputting data in to the template in input mode. However it is important to refer back to the actual code and ensure we have covered all possible scenarios.

  • We have not written any tests based on functionality as this would be complete as part of the functional review testing.

  • No hidden unreconciled logic present that we need to consider.

  • The results already present in the template contain all the necessary end values we need to test the scenario’s, so we don’t have to worry about adding new results for the liquid testing.

  • Tests have not been written for legacy code.

Test suite structure

  • I’ve started with the success paths since these are the most common and important scenario’s to test. So if these tests fail, they are the first to get focused.
  • I’ve also added error paths to test if the unreconciled logic and values are always behaving correctly.

Make use of anchors and aliases

  • In the empty state I’ve created anchors for global values that are applicable to all scenario’s and tests (context / company / results that are always created). This way if we change any general data or add new results that affect all tests, we can change that on one place.

Results

In this case, the template was already creating results for the end values so no new result creation was necessary.

Documentation

A documentation was attached to the test suite in order to go over the reasoning behind the testing setup and other people (and probably yourself one year in the future), can understand why the tests are setup in this way (see below).

Testing examples

  1. Calculations as above i.e. net wages, tax liability, pension liability. We are testing that these have been calculated correctly.

Net wages = Gross - PAYE - E’EE NI - E’EE pension - Other tax deductions - Other deductions
Tax liability = PAYE + E’EE NI + Other tax deductions + E’ER NI + Other reliefs
Pension liability = E’EE pension + E’ER pension
[Image: Screenshot 2022-03-07 at 14.06.49.png]

  1. Hashtag account selector inputs x3 - see above for explanation re configuration. These will feed through into the reconciliations below and will determine what the carried forward balance should be matching to.

[Image: Screenshot 2022-03-07 at 14.10.19.png]

  1. Option where “director” is selected and the subtotal calculation appears and relevant totals are added below.

[Image: Screenshot 2022-03-07 at 14.11.03.png][Image: Screenshot 2022-03-07 at 14.11.18.png]

  1. Fori loop x3. We are testing for the three reconciliations, national insurance, net wages, and pensions that amounts can be inputted into the reconciliation as shown below.

[Image: Screenshot 2022-03-07 at 14.12.52.png]

  1. Average no of employees calculation - this would be classified as unit 2. The data inputted here and the result does not affect the rest of the template.

[Image: Screenshot 2022-03-07 at 14.13.26.png]

# empty state for period after 21/02/2021 
empty_state2: 
  context:
    period: 2021-12-31
  data:
    periods:
      2021-12-31:
        reconciliations:
          newreconkalpna:
  expectation: 
    reconciled: false 
    results: 
      default_selected_hmrcLiability: "240501,240507,250501,250507"
      social_insurance_difference: 0 
      default_selected_wagesLiability: "240503,250503" 
      wages_difference: 0 
      default_selected_pensionLiability: "240504,250504" 
      pension_difference: 0 

# first test (net wages calculation)
net_wages: 
  context: 
    period: 2020-12-31
  data: 
    periods:
      2020-12-31: 
        reconciliations: 
          newreconkalpna:
            custom:
              payroll.payroll_1:
                paye: "10"
                gross: "100"
                employee_ni: "10"
                employee_pension: "10"
                other_deductions: "5"
                other_tax_deductions: "5"
  
  expectation: 
    reconciled: false 
    results: 
      default_selected_hmrcLiability: "240501,240507,250501,250507"
      social_insurance_difference: -25
      default_selected_wagesLiability: "240503,250503" 
      wages_difference: 0 
      default_selected_pensionLiability: "240504,250504" 
      pension_difference: -10 

# second test (hashtag input social insurance)
hashtag_si: 
  context:
    period: 2021-12-31
  data: 
    periods: 
      2021-12-31:
        accounts: 
          "240501":
            name: "other taxation and social security payable"
            value: 23000 
        reconciliations:
          newreconkalpna: 
  expectation:
    reconciled: false 
    results: 
      <<: *expectation_blueprint 
      default_selected_hmrcLiability: "240501,240507,250501,250507"
      default_selected_pensionLiability: "240504,250504"
      default_selected_wagesLiability: "240503,250503"
      social_insurance_difference: -23000

# third test (hashtag input wages control)
hashtag_wc: 
  context: 
    period: 2020-12-31
  data: 
    periods: 
      2020-12-31: 
        accounts: 
          "250503": 
            name: "wages and salaries"
            value: 40000
        reconciliations:
          newreconkalpna: 
  expectation: 
    reconciled: false 
    results: 
      <<: *expectation_blueprint
      wages_difference: -40000

# fourth test (hashtag input pension control)
hashtag_pc: 
  context:
    period: 2020-12-31 
  data: 
    periods: 
      2020-12-31:
        accounts: 
          "250504":
            name: "pensions payable"
            value: 13000
        reconciliations: 
          newreconkalpna: 
  expectation: 
    reconciled: false 
    results: 
      <<: *expectation_blueprint
      pension_difference: -13000

# fifth test (tax liability calculation)
tax_liability: 
  context:
    period: 2020-12-31
  data: 
    periods:
      2020-12-31: 
        reconciliations: 
          newreconkalpna: 
            custom: 
              payroll.payroll_1: 
                paye: "10"
                employee_ni: "10"
                employer_ni: "20"
                other_reliefs: "5"
                other_tax_deductions: "5"

  expectation: 
    reconciled: false 
    results: 
      default_selected_hmrcLiability: "240501,240507,250501,250507"
      social_insurance_difference: -50
      default_selected_wagesLiability: "240503,250503"
      wages_difference: 25
      default_selected_pensionLiability: "240504,250504"
      pension_difference: 0 

# sixth test (pension liability calculation)
pension_liability: 
  context:
    period: 2020-12-31
  data:
    periods:
      2020-12-31: 
        reconciliations: 
          newreconkalpna: 
            custom: 
              payroll.payroll_1: 
                employee_pension: "10"
                employer_pension: "20"

  expectation: 
    reconciled: false 
    results: 
      default_selected_hmrcLiability: "240501,240507,250501,250507"
      social_insurance_difference: 0
      default_selected_wagesLiability: "240503,250503"
      wages_difference: 10
      default_selected_pensionLiability: "240504,250504"
      pension_difference: -30

# seventh test (subtotal directors)
subtotal_directors:
  context: 
    period: 2020-12-31
  data: 
    periods: 
      2020-12-31: 
        reconciliations:
          newreconkalpna: 
            custom: 
              payroll.payroll_2: 
                paye: "30"
                gross: "300"
                director: true
                employee_ni: "40"
                employer_ni: "90"
                other_reliefs: "100"
                paid_net_salary: "170"
                employee_pension: "50"
                employer_pension: "1000"
                other_deductions: "70"
                other_tax_deductions: "60"
  
  expectation: 
    reconciled: false 
    results: 
      default_selected_hmrcLiability: "240501,240507,250501,250507"
      social_insurance_difference: -320 
      default_selected_wagesLiability: "240503,250503"
      wages_difference: 170 
      default_selected_pensionLiability: "240504,250504"
      pension_difference: -1050
      

# eighth test (fori loop on si control)
fori_loop1: 
  context: 
    period: 2020-12-31 
  data: 
    periods: 
      2020-12-31: 
        reconciliations: 
          newreconkalpna: 
            custom: 
              hmrc.hmrc_1: 
                dr: "23035"

  expectation: 
    reconciled: true 
    results: 
      default_selected_hmrcLiability: "240501,240507,250501,250507"
      social_insurance_difference: 0 
      default_selected_wagesLiability: "240503,250503"
      wages_difference: 0 
      default_selected_pensionLiability: "240504,250504"
      pension_difference: 0 

# ninth test (fori loop on wages control)
fori_loop2: 
  context: 
    period: 2020-12-31 
  data: 
    periods: 
      2020-12-31: 
        reconciliations: 
          newreconkalpna: 
            custom: 
              wages.wages_1:
                cr: "40230"
  
  expectation:
    reconciled: true 
    results: 
      default_selected_hmrcLiability: "240501,240507,250501,250507"
      social_insurance_difference: 0
      default_selected_wagesLiability: "240503,250503"
      wages_difference: 0
      default_selected_pensionLiability: "240504,250504"
      pension_difference: 0

# tenth test (fori loop on pension control)
fori_loop3: 
  context: 
    period: 2020-12-31 
  data: 
    periods: 
      2020-12-31: 
        reconciliations: 
          newreconkalpna: 
            custom:
              pensions.pension_1: 
                cr: "12955"
  
  expectation:
    reconciled: false 
    results: 
      default_selected_hmrcLiability: "240501,240507,250501,250507"
      social_insurance_difference: 0
      default_selected_wagesLiability: "240503,250503"
      wages_difference: 0
      default_selected_pensionLiability: "240504,250504"
      pension_difference: -12955