Skip to content

Testing what an action refuses ​

Task: prove that a form refuses what it should — a field that breaks its rule, a value the business does not allow — and that nothing is written when it does.

A sign-up with two kinds of refusal: the q:param rules (checked before the action runs) and a business rule in the action itself (the address is already a member):

yaml
# Recipe: testing what an action refuses — field rules and a business rule.
paths:
  components: ./components
  migrations: ./migrations

datasources:
  db:
    driver: sqlite
    database: ./data/signup.db
sql
CREATE TABLE members (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE
);
INSERT INTO members (name, email) VALUES ('Ana', 'ana@example.com');
xml
<q:component name="Signup">
  <q:action name="join" method="POST">
    <q:param name="name" required="true" minlength="2" />
    <q:param name="email" type="email" required="true" />

    <q:query name="taken" datasource="db">
      SELECT id FROM members WHERE email = :email
      <q:param name="email" value="{email}" type="string" />
    </q:query>
    <q:if condition="taken_result.recordCount > 0">
      <q:redirect url="/" flash="{email} is already a member." flashType="error" />
    </q:if>

    <q:query name="added" datasource="db">
      INSERT INTO members (name, email) VALUES (:name, :email)
      <q:param name="name" value="{name}" type="string" />
      <q:param name="email" value="{email}" type="string" />
    </q:query>
    <q:redirect url="/" flash="Welcome, {name}!" />
  </q:action>

  <h1>Join</h1>
  <q:if condition="flash"><p class="flash-{flashType}">{flash}</p></q:if>
  <form method="POST" action="/?action=join">
    <input name="name" />
    <input name="email" type="email" />
    <button>Join</button>
  </form>
</q:component>

error="name" checks the field the submit was refused on, and message= the text shown next to it. A refusal the action decides itself is a redirect with a flash, checked with flash=. table= with count= proves nothing was written:

xml
<q:test name="a short name is refused on its field, with the rule's message" page="/">
  <test:submit action="join" name="A" email="bia@example.com" />
  <test:expect error="name" message="Must be at least 2 characters" />
  <test:expect table="members" count="0" where="email = 'bia@example.com'" />
</q:test>

<q:test name="an address that is not an e-mail is refused on its field" page="/">
  <test:submit action="join" name="Bia" email="bia-at-example" />
  <test:expect error="email" />
  <test:expect table="members" count="1" />
</q:test>

<q:test name="an address that is already a member is refused with a flash" page="/">
  <test:submit action="join" name="Ana Again" email="ana@example.com" />
  <test:expect redirect="/" flash="ana@example.com is already a member." />
  <test:expect table="members" count="1" />
</q:test>

<q:test name="a new member is welcomed" page="/">
  <test:submit action="join" name="Bia" email="bia@example.com" />
  <test:expect redirect="/" flash="Welcome, Bia!" />
  <test:expect table="members" count="1" where="email = 'bia@example.com'" />
</q:test>
text
tests/signup.test.q
  PASS  a short name is refused on its field, with the rule's message
  PASS  an address that is not an e-mail is refused on its field
  PASS  an address that is already a member is refused with a flash
  PASS  a new member is welcomed
4 passed, 0 failed

The rules a q:param takes (required, minlength, type="email", enum…) are in Actions & Forms.

Tested: this page imports the files of examples/cookbook/testing/action-errors/, and the result above is the report of running them.

MIT Licensed · Built with VitePress