Having recently used Fancordion to test a web application I just wanted to say how much I'm loving it!

Fancordion tests are expressive, powerful, easy to write, AND they look good!

More than that, because Fancordion specifications are written in plain English they explain the WHY behind the functionality under test. It helps inform you, and others, of how important that functionality is. Useful, because at some point (when the requirements change yet again) you're gonna want to change or delete that functionality.

So, what have I been doing?

I've been rewriting the Bushmasters website; Bushmasters being a small company who specialise in Jungle Survival adventure holidays deep in the Amazon. I used to work for them - hence you can find me in a lot of their online photographs. For example, here's me ready to explore parts of the Amazon where no man has been before, and there's plenty of me in this collection of Venture photographs.

Bushmasters Logo

Anyway, back to programming...

Fancordion lets you write your tests in plain English; no structured given / when / then, no regular expressions. Just readable English with a smattering of fandoc markup. When run, Fancordion generates HTML results. Verifications that pass are highlighted in green, and those that fail are highlighted in red; complete with embedded stacktraces.

The Concordion Technique page has some great hints and tips, do's and don'ts, on how to write good acceptance tests. My preference is to write a little about the problem and follow it up with a worked example; with the test actually performed in the example.

Following is one of the (many) Fancordion tests written. Quotes from Bushmaster customers are categorised by the trip / type of holiday they were on: survival, cowboy, safari, etc... This test verifies that quotes only appear on webpages that relate to their category.

Let's start by looking at the output - the generated HTML:

Fancordion Test Result

Firstly, the green highlights tell me that all went well and the test passed. Hooray!

Other points of interest on the page are:

  • The breadcrumbs at the top of the page. These are automatically generated.
  • The table, generated from special (Fancordion) fandoc markup.
  • The styling of the Example section - added automatically.
  • The links at the bottom of the page, used for navigation and to run other tests.

Now lets look at the source code for the test:

select all
using afBounce

** Displaying Customer Quotes
** ##########################
** As Bushmasters I want to promote the good times had by previous customers by advertising
** their quotes and comments on the website. As I envision having many, many quotes to show,
** I want to categorise them so only relevant quotes are shown on the various trip pages.
** Example
** -------
** Given the system only holds the following quotes:
**   table:
**   row+exe:createQuote(#COL[0], #COL[1], #COL[2])
**   Category  Quotee      Quote
**   --------  ----------  ----------
**   Jungle    John Rambo  Fantastic!
**   Vaquero   John Wayne  Coolio!
** When I visit the [survival]`exe:findQuoteOnPage(#TEXT)` page I should see the following:
** Quote:  ["Fantastic!"]`eq:data["quote"]` and
** Quotee: [John Rambo]`eq:data["quotee"]`
** And when I visit the [cowboy]`exe:findQuoteOnPage(#TEXT)` page I should see the following:
** Quote:  ["Coolio!"]`eq:data["quote"]` and
** Quotee: [John Wayne]`eq:data["quotee"]`
** Further Reading
** ===============
**  - How do I [create Quotes]`run:TestQuotesCreate#`?
**  - How do I [edit Quotes]`run:TestQuotesEdit#`?
**  - How do I [delete Quotes]`run:TestQuotesDelete#`?
class TestShowCustomerQuotes : BushFixture {
    Str:Str? data    := [:]

    override Void setupFixture() {
        cmsPageDao.update(newCmsPage("Survival") { it.quoteCategories = [QuoteCategory.jungle]  })
        cmsPageDao.update(newCmsPage("Cowboy")   { it.quoteCategories = [QuoteCategory.vaquero] })

    Void createQuote(Str category, Str quotee, Str quote) {
        quoteDao.create(newQuote() {
            it.category = QuoteCategory.fromStr(category.fromDisplayName)
            it.quotee = quotee
            it.quote  = quote

    Void findQuoteOnPage(Str page) {
        data["quote"]  = Element(".quote").text
        data["quotee"] = Element(".quotee").text

You can see where the generated HTML came from by looking at class documentation; this is the specification that drives the test.

The meat of the class contains methods referenced by the specification (class documentation). It does not amount to much more than 10 lines of real code. And those 10 lines do all this:

  • Creates sample web pages
  • Creates sample quotes
  • Visits each sample web page
  • Scrapes the web page for quote data
  • Asserts the quote is the one expected

(Wow - it's just taken me 5 lines to explain those 10 lines!)

The cynics amongst you, and those used to other frameworks, are probably thinking, "Ah, most of the test code is in the super class and other helper classes." - Not true.

To prove it, lets look at the code in detail:

  • The BushFixture superclass is almost an exact copy of WebFixture as described in the Fancordion documentation for BedSheet.

    It contains the cmsPageDao and quoteDao fields and the methods newCmsPage() and newQuote(). The DAOs are @Injected by IoC and the newXXX() methods simply create default entity objects.

  • setupFixture() creates some sample webpages.

    The update() method on the DAO is a simple Morphia call.

  • createQuote() does just that.

    It is invoked for each row in the table in the specification.

  • findQuoteOnPage() is invoked by the specification. client is a BedClient from Bounce and is used to navigate to the webpage in question.

    HTML Elements from Bounce and basic CSS class selectors ( .quote and .quotee ) are used to scrape text off the webpage and stick them in the data field.

  • The verification commands in the specification, such as eq:data["quote"], then assert that the data field contains what was expected.

And that's it! So much in so little!

Fancordion, I'm loving it!

Have fun!