BounceUser Guide
Overview
Bounce - use it to test your BedSheet Apps!
Bounce is a testing framework that makes requests to your Bed App without the expensive overhead of starting a web server, opening ports, and making network connections.
Bounce uses rich CSS selectors and a simple API to let you query and verify your web pages. In fact, it's pretty much a headless browser!
Quick Start
- Create a text file called
Example.fan
:using afBounce using afBedSheet using afIoc class Example : Test { Void testBedApp() {
// givenserver := BedServer(AppModule#).startup client := server.makeClient// whenclient.get(`/index`)// thentitle := Element("#title") title.verifyTextEq("Bed Bouncing!")// clean upserver.shutdown } }** A Really Simple Bed App!!!const class AppModule { @Contribute { serviceType=Routes# } Void contributeRoutes(OrderedConfig config) { config.add(Route(`/index`, Text.fromHtml("""<html><p id="title">Bed Bouncing!</p></html>"""))) } } - Run
Example.fan
as a Fantom test script ( fant ) from the command line:C:\> fant Example.fan -- Run: Example_0::Example.testBedApp... [info] [afIoc] Adding modules from dependencies of 'afBedSheet' [info] [afIoc] Adding module definition for afBedSheet::BedSheetModule [info] [afIoc] Adding module definition for afIocConfig::IocConfigModule [info] [afIoc] Adding module definition for afIocEnv::IocEnvModule [info] [afIoc] Adding module definition for Example_0::AppModule [info] [afIoc] ___ __ _____ _ / _ | / /_____ _____ / ___/__ ___/ /_________ __ __ / _ | / // / -_|/ _ /===/ __// _ \/ _/ __/ _ / __|/ // / /_/ |_|/_//_/\__|/_//_/ /_/ \_,_/__/\__/____/_/ \_, / Alien-Factory BedServer v1.0.16, IoC v2.0.0 /___/ BedServer started up in 597ms Pass: Example_0::Example.testBedApp [1] Time: 2052ms *** *** All tests passed! [1 tests, 1 methods, 1 verifies] ***
Usage
Use BedServer to start an instance of your Bed App, and then use BedClient make repeated requests against it. The HTML elements are then used to verify that correct content is rendered.
BedClient
is a ButterDish
that wraps a Butter
instance - all functionality is provided by Butter middleware. BedTerminator is the terminator of the stack, which sends requests to BedServer, which holds the instance of your Bed App.
Verify HTML Content
When queried, the HTML classes ( Element, TextBox, etc...) use the last response from the client. The client stores itself in the Actor.locals()
map, and the HTML elements implicitly use this value. This means you can define your Elements once (in a mixin if need be) and use them over and over without needing to track the response they're querying. Example:
// deinfe your elementstitle := Element("h1") link := Element("#page2")// use themclient.get(`/index`) title.verifyTextEq("Home Page") link.click title.verifyTextEq("Page2")
Inspect the WebSession
It is often useful to inspect, assert against, or even set values in, a client's web session. As the BedClient
holds the session, this is easy!
using afBounce class TestMyBedApp : Test { Void testBedApp() { server := BedServer(AppModule#).startup client := server.makeClient ....// set values in the user's sessionclient.webSession["userName"] = "Emma"// assert against session valuesverifyNotNull(client.webSession["shoppingCart"]) } }
Inject Services Into Tests
BedServer
has access to the IoC registry used by your Bed App, this lets you inject services into your test.
using afBounce using afIoc class TestMyBedApp : Test { @Inject MyService myService Void testBedApp() { server := BedServer(AppModule#).startup// inject services into testserver.injectIntoFields(this) ... } }
Override Services
BedServer
lets you specify additional Ioc modules, letting you add custom test modules that override or stub out real services with test ones.
using afBounce class TestMyBedApp : Test { Void testBedApp() { server := BedServer(AppModule#)// override services with test implementationsserver.addModule(TestOverrides#) server.startup .... } }
Test REST Apps
BedClient
extends ButterDish and so comes complete with convenience methods for calling RESTful services.
GET
For a simple GET request:
client := bedServer.makeClient response := client.get(`http://example.org/`)
POST
To send a POST request:
client := bedServer.makeClient jsonObj := ["wot" : "ever"] response := client.postJsonObj(`http://example.org/`, jsonObj)
PUT
To send a PUT request:
client := bedServer.makeClient jsonObj := ["wot" : "ever"] response := client.putJsonObj(`http://example.org/`, jsonObj)
DELETE
To send a DELETE request:
client := bedServer.makeClient response := client.delete(`http://example.org/`)
Custom
For complete control over the HTTP requests, create a ButterRequest and set the headers and the body yourself:
client := bedServer.makeClient request := ButterRequest(`http://example.org/`) { it.method = "POST" it.headers.contentType = MimeType("application/json") it.body.str = """ {"wot" : "ever"} """ } response := client.sendRequest(req)
Test Outside The Box!
By creating BedClient
with a Butter
stack that ends with a real HTTP terminator, Bounce
can also be used to test web applications in any environment. Example:
using afBounce using afButter class TestFantomFactory : Test { Void testFantomFactory() {// add Sizzle to the default middleware stackclient := BedClient(Butter.churnOut( Butter.defaultStack.insert(0, SizzleMiddleware()) ))// make real http requests to your integration environmentclient.get(`http://www.fantomfactory.org/pods/afBounce`)// use sizzle to testtagLine := Element(".jumbotronic h1 + p") tagLine.verifyTextEq("A library for testing Bed applications!") } }
Not Just for Bed Apps!
The HTML element classes ( Element, TextBox, etc...) are not just for testing Bed Applications! By setting a SizzleDoc instance in Actor.locals()
with the key afBounce.sizzleDoc
you can use the HTML classes with any HTML:
using afBounce using afSizzle class TestHtml : Test { Void testHtml() { xhtml := "<html xmlns="http://www.w3..." // --> your XHTMLActor.locals["afBounce.sizzleDoc"] = SizzleDoc.fromStr(xhtml) success := Element("span.success") success.verifyTextEq("Awesome!") Actor.locals.remove("afBounce.sizzleDoc") } }
Multipart Forms / File Uploads
Bounce also handles file uploads!
Ensure the form has an enctype
of multipart/form-data
with a method
of POST
, then set any file input values to the OS path of the file you want uploaded. It's that simple!
For example, if the HTML looks like this:
<form action="..." method="POST" enctype="multipart/form-data"> <input id="file" name="file" type="file" > <button id="submit" name="submit" type="submit">Upload</button> </form>
Then upload a file with:
FormInput("#file").value = `/tmp/myFile.zip`.toFile.osPath SubmitButton("#submit").click