Karate Coding Standards & Best Practices

Coding standards and best practices are very important, especially as applications scale up and become more complex. Adhering to these guidelines enhances readability, maintainability, efficiency, collaboration, knowledge sharing etc. By adopting a standardized approach, we can ensure that our test codebase is easily understandable, reusable, and adaptable to changes in requirements.

Follow Scenario Format & Structure

  • Use tags to categorize and group related scenarios

  • Each feature should be written in a way such that the file will receive input(s) like credentials, tokens, reference data identifiers. Other prerequisites like tenant creation can be composed with the feature. The feature file should look like it can executed in an existing tenant or a new tenant(ignoring reference data dependencies).

  • Store variables linking external references like JSON files & reusable functions at the top of the feature file, preferably in the Background section.

Feature: [Feature Name] Background: * url baseUrl * configure headers = read('classpath:headers.json') * def commonFunctions = call read('classpath:common/commonFunctions.feature') Scenario: [Scenario Name] # This is a comment explaining the purpose of the scenario Given path '[endpoint]' And request read('classpath:requests/[request_file].json') When method [HTTP_METHOD] Then status [expected_status_code] And match response == read('classpath:responses/[response_file].json') And def result = call commonFunctions.[reusable_function_name] Scenario Outline: [Scenario Outline Name] Given path '[endpoint]' And request read('classpath:requests/[request_file].json') And set request.[parameter] = <value> When method [HTTP_METHOD] Then status [expected_status_code] And match response == read('classpath:responses/[response_file].json') And def result = call commonFunctions.[reusable_function_name] Examples: | parameter | value | | [param1] | [value1] | | [param2] | [value2] | Scenario: [Another Scenario Name] Given path '[endpoint]' And request read('classpath:requests/[request_file].json') When method [HTTP_METHOD] Then status [expected_status_code] And def expectedResponse = read('classpath:responses/[response_file].json') And match response.[field1] == expectedResponse.[field1] And match response.[field2] == expectedResponse.[field2] And def result = call read('classpath:common/[reusable_feature_file].feature')

Ensure Independent Scenarios

  • Design scenarios to be independent and self-contained, avoiding dependencies on the execution order

  • Reset or clean up any necessary state or data between scenarios to maintain independence. A heuristic that can be used to determine data clean is “can i run my scenario over and over again?”. If the answer to the question is “No”, then clean up should be performed.

  • Each scenario should create their own data to use, unless declared in Background section of the feature file.

Add Commentary On Scenarios

  • Use comments to document any assumptions, limitations, or known issues related to the scenario.

  • Keep comments concise and relevant, avoiding unnecessary or redundant information.

Prioritize Simplicity & Clarity, Favor Readability Over Complexity

  • Write scenarios in a clear and concise manner, focusing on the essential steps and assertions.

  • Avoid complex or nested logic within scenarios. Opt to utilize java functions for heavy coding scenarios. The scenarios should read like a technical product owner wrote it.

  • Use built-in Karate functions and utilities whenever possible to keep the code clean and maintainable.

Employ Reusable Functions

  • Define reusable functions in a separate file or directory and import them into the feature files as needed.

  • Use descriptive names for reusable functions to convey their purpose and functionality.

  • Document the input parameters and return values of reusable functions using comments.

Avoid Very Large Feature Files

  • Keep feature files focused and concise, avoiding the creation of very large files that encompass too many scenarios.

  • Split large feature files into smaller, more manageable files based on logical groupings or related functionality.

  • Use the call keyword to invoke reusable scenarios or functions from other feature files when necessary.

Externalize Huge JSON Objects

  • If a scenario requires large or complex JSON objects, consider externalizing them into separate files.

  • Use the read() function to load the JSON objects from external files and reference them within the scenario.

  • Keep the external JSON files organized and properly named to maintain clarity and maintainability.

Curb Use of Global Variables

  • Minimize the use of global variables and prefer local variables within scenarios whenever possible.

  • Document the purpose and usage of global variables using comments.

Do Not Repurpose the Same Identifiers

  • Each scenario should have its own unique set of data identifiers to ensure independence and avoid potential conflicts or side effects.

  • If scenarios rely on pre-existing data, consider creating separate test data sets for each scenario or using dynamic data generation techniques to ensure uniqueness.

  • When creating test data or referring to specific data identifiers (e.g., order IDs, user IDs & other UUIDs) in your scenarios, avoid using the same identifiers across multiple scenarios.

Asserting Objects/Collections In a Response

  • 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.

    // 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.

    // 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

Naming Conventions

  • Use descriptive and meaningful names for feature files, scenarios and variables.

  • Use snake_case convention throughout the test suite.

  • Use uppercase with underscores for constant names to differentiate them from variables.