FLAT
CouperSevenval TechnologiesDocker ImageGithub
master
master
  • Changelog
  • FLAT
  • Administration
    • Configuration
    • Docker
    • Logging
  • Cookbook
    • Using the Built-in Mocking
    • Performing Additional Checks on JWT Access Tokens
    • Logging Custom Fields
    • Using Environment Variables
    • Handling Errors with an Error Flow
    • File Serving
    • Forwarding a Request to an Upstream API
    • Extracting Common Initialization Flow Tasks
    • Encoding and Decoding JWT
    • Passing Header Fields to the Client
    • How can I pass an arbitrary header field to an upstream system?
    • Performing Additional Checks on JWT Access Tokens
    • Proxying requests to Upstream APIs
    • Increasing the Request Timeout
    • How can I see what the client requested?
    • Using Swagger UI for API Documentation
    • Testing API Requests
    • Testing with Backend Requests
    • Testing Templates
    • Sending POST Requests
    • Processing Upstream Responses
    • Protecting Access using JWT Tokens
  • Reference
    • Configuration
    • Debugging
    • flat CLI
    • Flow
    • Variables
    • OpenAPI / Swagger Integration
    • OpenAPI
      • CORS - Cross-Origin Resource Sharing
    • OpenAPI
      • Differences from Swagger
    • OpenAPI
      • Mocking
    • OpenAPI
      • Routing
    • OpenAPI
      • Security
    • OpenAPI
      • Upstream APIs
    • OpenAPI
      • Validation
    • Flow Actions
      • assert Action
      • auth Action
      • backend-flow Action
      • copy Action
      • debug Action
      • dump Action
      • echo Action
      • error Action
      • eval Action
      • log Action
      • nameshave Action
      • pass-body Action
      • proxy-request Action
      • regex Action
      • request Action
      • requests Action
      • serve Action
      • set-config Action
      • set-env Action
      • set-response-headers Action
      • set-status Action
      • sub-flow Action
      • template Action
      • test-request Action
      • xslt Action
    • Functions
      • apply-codecs()
      • array-reverse()
      • array()
      • base64-decode()
      • base64-encode()
      • body()
      • calc-signature()
      • capitalize-first()
      • content()
      • decrypt-xml()
      • decrypt()
      • encrypt()
      • ends-with()
      • file-exists()
      • fit-document()
      • fit-log()
      • fit-serialize()
      • get-log()
      • has-class()
      • html-parse()
      • join()
      • json-doc()
      • json-parse()
      • json-stringify()
      • json-to-csv()
      • json-to-xml()
      • jwt-decode()
      • jwt-encode()
      • ldap-lookup()
      • ldap-query()
      • lookup()
      • matches()
      • md5()
      • replace()
      • sort()
      • split()
      • tolower()
      • toupper()
      • trim()
      • unixtime()
      • urldecode(), url-decode()
      • urlencode(), url-encode()
      • uuid3() and uuid4()
      • verify-signature()
      • verify-xmldsig()
      • xml-parse()
      • xml-to-json()
    • Templating
      • {{,}}
      • Comment {{// …}}
      • Dot {{.}}
      • Conditional `{{if <condition>}} … {{elseif <condition> }} … {{else}} … {{end}}
      • loop
      • ?? Operator
      • Object XML Notation (OXN)
      • Pair Producer {{: …}}
      • Placeholder
      • Template Variables
      • with
    • Testing
  • Tutorial
Powered by GitBook
On this page
  • Our Test Specimen
  • Writing a Test
  • Refactor for Testing
  • JSON File Comparisons
  • Full Example
  • Adding More Tests
  • Using Other Compare Flags
  • See also

Was this helpful?

  1. Cookbook

Testing Templates

PreviousTesting with Backend RequestsNextSending POST Requests

Last updated 5 years ago

Was this helpful?

You have written a and now you want to test it. FLAT comes with a that makes it easy to execute your code with various input and error conditions.

Our Test Specimen

Let's write a simple template that outputs some information on the incoming request. (You have to register it in your , we assume it is stored as api/request-info.xml).

<flow>
  <template>
  {
    "method": {{ $request/method }},
    "path": {{ $request/path }},
    "numPostFields": {{ count($request/post/*) }},
    {{if $request/get/data }}
      "data": {{ $request/get/data }}
    {{end}}
  }
  </template>
</flow>

Looks easy. Let's try it out:

$ curl -s -d 'a=b&c=d' localhost:8080/api/path?data=foo |jq

The result is:

{
  "method": "POST",
  "path": "/api/path",
  "numPostFields": 2,
  "data": "foo"
}

So far, so good. If you look closely, you will notice that the template is buggy. But we will bump into this when writing tests for it :)

Writing a Test

Usually, tests should run in the context of the FLAT app. Therefore, we recommend putting tests in a tests/ folder next to the swagger.yaml:

$ ls my-project
swagger.yaml
api/
htdocs/
tests/

A test usually comprises

  • the flow code under test

  • setup code to provide input/env data

Create tests/test-request-tpl.xml:

<flat-test>
  <!-- setup input data -->
  <template out="$request">
  {
    "method": "POST",
    "path": "/a/path",
    "post": {
      "a": 1,
      "b": 2,
      "c": 3
    },
    "get": {
      "data": "foo"
    }
  }
  </template>

  <!-- code under test -->
  <template out="$result">
  {
    "method": {{ $request/method }},
    "path": {{ $request/path }},
    "numPostFields": {{ count($request/post/*) }},
    {{if $request/get/data }}
      "data": {{ $request/get/data }}
    {{end}}
  }
  </template>

  <!-- assertions -->
  <assert>
  [
    [ "json-stringify($result)", "{\"method\":\"POST\",\"path\":\"/a/path\",\"numPostFields\":3,\"data\":\"foo\"}" ]
  ]
  </assert>
</flat-test>
$ flat test tests/test-request-tpl.xml
1..1
ok 1 tests/test-request-tpl.xml: 1 assertions
passed: 1, failed: 0

Great! But there's still room for improvement…

Refactor for Testing

In the test, we had to copy the template from our production code. This doesn't really make sense. Because, now we test an isolated copy. If we break the actual flow, the test will still pass.

There are a couple of options to go about this.

We could extract the template body into a file:

<flow>
  <template src="request.tpl" />
</flow>

Now, in the test, we only repeat that action call:

<flat-test>
  …
  <template src="../api/request.tpl" />
  …
</flat-test>

Note, that we provide a different src attribute, because the relative path to the template differs for the flow and the test file.

<flat-test>
  …
  <sub-flow src="../api/request-info.xml" />
  …
</flat-test>

This looks much better! We now test the real template call.

<assert>
[
  [ "json-stringify(content())", "{\"method\":\"POST\",\"path\":\"/a/path\",\"numPostFields\":3,\"data\":\"foo\"}" ]
]
</assert>

JSON File Comparisons

But we can also make the assertion part of tests look better. For the beginning we were explicitly using json-stringify() to turn $result into a string. And even worse, that string had to be compared to a clunky, escaped JSON string.

The comparison parameter (2nd field of assertions) can also be an object with special flags. We can instruct assert to read the comparison value from a file:

<assert>
[
  [ "json-stringify($response)", {"file": "request-info.golden"} ]
]
</assert>

Now we can put the expected JSON string into the golden file tests/request-info.golden:

{"method":"POST","path":"/a/path","numPostFields":3,"data":"foo"}

We can still simplify that by using the json comparison mode:

<assert>
[
  [ "$response", {"file": "request-info.golden", "mode": "json"} ]
]
</assert>

The json mode ensures that both wanted and actual results are valid JSON. The comparison is independent of the JSON formatting. This allows us the have the golden file pretty printed:

{
  "method": "POST",
  "path": "/a/path",
  "numPostFields": 3,
  "data": "foo"
}

This makes changes to the file easier during development. Especially your co-workers will thank you for a readable git diff.

Full Example

api/request-info.xml:

<flow>
  <template>
  {
    "method": {{ $request/method }},
    "path": {{ $request/path }},
    "numPostFields": {{ count($request/post/*) }},
    {{if $request/get/data }}
      "data": {{ $request/get/data }}
    {{end}}
  }
  </template>
</flow>

tests/test-request-tpl.xml:

<flat-test>
  <!-- setup input data -->
  <template out="$request">
  {
    "method": "POST",
    "path": "/a/path",
    "post": {
      "a": 1,
      "b": 2,
      "c": 3
    },
    "get": {
      "data": "foo"
    }
  }
  </template>

  <!-- code under test -->
  <sub-flow src="../request-info.xml" />

  <assert>
  [
    [ "content()", {"file": "request-info.golden", "mode": "json"} ]
  ]
  </assert>
</flat-test>

tests/request-info.golden:

{
  "method": "POST",
  "path": "/a/path",
  "numPostFields": 3,
  "data": "foo"
}

Call test:

$ flat test tests/test-request-tpl.xml
1..1
ok 1 tests/test-request-tpl.xml: 1 assertions
passed: 1, failed: 0

Adding More Tests

So far, we have only tested one specific input. Let's provide another test with different input data, such as a GET request or one without the data query parameter. This will reveal a serious bug in our code!

You can copy the test to another file for the GET case. If you think those variants are closely related, you can also add more test code after the <assert> in our tests/test-request-tpl.xml like this:

<flat-test>

  <!-- test case from above -->
  …
  </assert>

  <!-- test GET -->
  <template out="$request">
  {
    "method": "GET",
    "path": "/"
  }
  </template>

  <sub-flow src="../request-info.xml" />

  <assert>[[ "content()", {"file": "request-info2.golden", "mode": "json"} ]]</assert>
</flow>

tests/request-info2.golden could look like this:

{
  "method": "GET",
  "path": "/",
  "numPostFields": 0
}

What happens, when you call the flat test command now?

We get an execution error! Luckily the debug (-d [topic]) is enabled, so we see:

Action "template": Output is not valid JSON: Syntax error

📝 Exercise: Fix that bug in the template!

After we have fixed the bug in the template, the test result should look like this:

$ flat test tests/test-request-tpl.xml
1..1
ok 1 tests/test-request-tpl.xml: 2 assertions
passed: 1, failed: 0

💡 Hint: You can call flat test with multiple files:

$ flat test tests/test-*.xml
1..2
ok 1 tests/test-request-tpl.xml: 1 assertions
ok 2 tests/test-request-tpl-get.xml: 1 assertions
passed: 2, failed: 0

Using Other Compare Flags

You can use more compare flags for your tests.

The contains flag can be used to test whether an expression result contains a given string:

<flat-test>
  <template out="$request">
  {
    "method": "POST",
    "path": "/a/path#"
  }
  </template>

  <assert>
  [
    [ "$request/path", {"contains": "a/"} ]
  ]
  </assert>
</flat-test>

This assertion tests whether the path property of $request contains the string "a/".

The pattern flag can be used to test whether an expression result matches a given regular expression:

  <assert>
  [
    [ "$request/method", {"pattern": "#^post$#i"} ]
  ]
  </assert>

This assertion tests whether the method property of $request matches case-insensitively the string "post".

See also

In FLAT, tests are written in <flat-test> XML files. Those tests work like regular , but they provide specialized to make testing easy.

The has a test command to run tests. It it easiest to run it from inside the FLAT app dir (where swagger.yaml resides).

Another approach is to extract the template code into a . This can be called both by the path flow and the test:

However, that call did not specify an out variable. To catch that template output in the test, we use the function:

💡 Hint: The number one reason for invalid JSON syntax are .

(cookbook)

(cookbook)

(reference)

(reference)

JSON Template
Test Framework
swagger.yaml
assertions
flat cli
sub flow
content()
commas
Testing API Requests
Testing Upstream Requests
Testing
assert
flows
actions