Skip to content

Testing With Stubs

Chris Michael edited this page Oct 6, 2026 · 1 revision

Pudu

Testing With Stubs

Stub is a primary handler answering from rules and recording every request, so code using a client is tested without sockets: configure the name with withPrimary(|| Stub.send(&stub)). Stub.respond answers a method and path with a status and text, Stub.on with a function, and Stub.when any request a predicate holds for; a request no rule answers gets 404.

module Ex11Testing

import Std.Http as Http
import Std.Io as Io
import Std.Test as Test
import PuduLangHttpClient as HttpClient
import PuduLangHttpClient.Client as Client
import PuduLangHttpClient.Factory as Factory
import PuduLangHttpClient.Factory.Builder as Builder
import PuduLangHttpClient.Request as Request
import PuduLangHttpClient.Response as Response
import PuduLangHttpClient.Stub as Stub

fn priceOf(client: &Client.Client, sku: Str) -> Result[Int, Str] {
  match Client.getText(client, "prices/" + sku) {
    case Ok(text) => match text.toInt() {
      case Some(cents) => Ok(cents)
      case None => Err("not a price: " + text)
    }
    case Err(failure) => Err(HttpClient.describe(&failure))
  }
}

export fn main() -> Int {
  let stub = Stub.create()
  Stub.respond(&stub, Http.Get, "/prices/tea", 200, "450")
  Stub.respond(&stub, Http.Get, "/prices/odd", 200, "lots")
  Stub.when(&stub, |request: Request.Request| request.uri.endsWith("/down"), |_request: Request.Request| Err(HttpClient.Connection("refused")))
  let factory = match Factory.build([Builder.named("prices").withBaseAddress("https://prices.test/").withPrimary(|| Stub.send(&stub))]) {
    case Ok(built) => built
    case Err(invalid) => panic(Factory.explain(&invalid))
  }
  let client = Factory.createClient(&factory, "prices")
  let checks = Test.suite("prices", &[
      Test.equals("a price is read", &priceOf(&client, "tea"), &Ok(450)),
      Test.equals("text that is not a price is refused", &priceOf(&client, "odd"), &Err("not a price: lots")),
      Test.equals("an unknown item is unsuccessful", &priceOf(&client, "gold"), &Err("The response status 404 (Not Found) does not indicate success.")),
      Test.equals("a broken connection is reported", &priceOf(&client, "down"), &Err("The connection failed: refused")),
      Test.equals("every request was recorded", &Stub.received(&stub).length(), &4)
    ])
  let ran = Test.run(&checks)
  for failure in Test.failuresOf(&ran) { let _said = Io.writeErrorLine(failure) }
  Test.report(&ran)
}

Check it and run it:

pudu check src/Ex11Testing.pudu
pudu test src/Ex11Testing.pudu

Output:

PASS  Ex11Testing.pudu  5 held

1/1 suites passed, 5 assertions held

Stub.received lists every request in order, for checking what the code sent. A rule may answer a failure, so connection problems are tested as easily as responses.

Related

Clone this wiki locally