Jesse Lawson

What to expect in Tasty’s upcoming release (0.9.6)


rust cli tasty

Three new changes are being shipped to Tasty today. The first two—granular expectations control and test response referencing—I wrote about in a previous blog post, and ship as part of 0.9.5. The third came from a recommendation in GitHub about adding headers to the command line, and ship as part of 0.9.6. Together, these changes let me finally migrate my entire API testing workflow to Tasty.

If you’re new here: Tasty is a CLI tool I built to run API tests defined in TOML files. I built it for myself, but I’ve been pleasantly surprised by how many others have found it useful for their own projects.

# What’s New from the Roadmap

This release delivers both features from the roadmap:

  1. Test Response Referencing! Subsequent tests can now pull values from prior test responses.
  2. Granular Expectations! A new expect key structure that supports both literal matching and regex patterns

Let me walk through what these look like in practice.

# Referencing Previous Responses

While testing out my authentication flow, I found myself needing to use a token from a previous test in a follow-on test. Before this release, I had to hardcode test tokens or run tests manually in sequence, copying and pasting returned values in between.

Now you can do this:

[test_login]
method = "POST"
route = "auth/login"
payload = { username = "test_user", password = "secret123" }
expect.http_status = 200
expect.response = { token_type = "Bearer" }

[test_protected_resource]
method = "GET"
route = "api/dashboard"
payload.auth_token = { from = "test_login", property = "access_token" }
expect.http_status = 200

The { from = "...", property = "..." } syntax tells Tasty to look up the response from a previous test and extract a value. The property field supports dot notation for nested access—so user.profile.id works exactly as you’d expect.

Notably, if the referenced test doesn’t exist or failed, the dependent test fails with a clear error message. This felt like the right behavior: if your auth test fails, you probably don’t want to silently send requests with missing tokens.

# The New Expect Syntax

This one is a breaking change, so let me explain the reasoning.

Up until now, here’s what testing an expected value looked like:

expect_http_status = 200
expect_response_includes = { token_type = "Bearer" }

Going forward, here’s what testing an expected value will look like with the new syntax:

expect.http_status = 200
expect.response = { token_type = "Bearer" }

On the surface this may seem like shuffling deck chairs—we’ve just moved things under an expect namespace. But what this gets at is enabling separate namespaces for different matching strategies.

During implementation, I realized the original roadmap’s approach of “all values are regex patterns” led my brain to having to overlay regex mental models on top of all my tests. But if you want to check that token_type equals exactly "Bearer", you shouldn’t have to think about regex escaping. After all, for the vast majority of what I’m doing, most expectations are just literal string comparisons.

So I ended up landing on two parallel namespaces:

expect.http_status = 200
expect.response = { token_type = "Bearer" }              # literal match
expect.response_regex = { access_token = "[a-zA-Z0-9]+" }  # regex match

So we have a literal match (expect.response) and we have a regex match (expect.response_regex). Both can coexist in the same test! Importantly, the literal checks run first, then the regex checks—regardless of test ordering. This may change in the future, but it wouldn’t necessarily be a breaking change. The way I’m accounting for this is just breaking out my literal expectation tests and regex expectation tests into separate tests. As you might expect, if any test fails, you get feedback showing exactly which expectation didn’t match and what the actual response contained.

# Migration

Since this is a breaking change, you have to update your existing test files.

  1. Migrate expect_http_status = X to expect.http_status = X
  2. Migrate expect_response_includes = { ... } to expect.response = { ... }
  3. If you had expect_response_includes = {} (checking for any valid JSON response), you can now just omit the expect.response key entirely—only expect.http_status is required.

# New command: headers!

Literally as I was preparing the v0.9.5 release, a contributor submitted a pull request adding support for custom HTTP headers via a CLI flag. This turned out to be a nice complement to the response referencing feature—where response referencing handles dynamic tokens extracted from test responses, the header flag handles static credentials you want applied to every request.

The syntax follows the familiar curl convention:

# Single header
tasty -b https://api.example.com -H "Authorization: Bearer mytoken"

# Multiple headers
tasty -b https://api.example.com -H "Authorization: Bearer token" -H "X-Api-Key: secret"

Headers are applied as defaults on the HTTP client, so every request in your test suite will include them. This is useful for API keys, static bearer tokens, or any other headers your API requires globally.

Between response referencing for dynamic auth flows and the -H flag for static credentials, Tasty now covers the authentication scenarios I actually encounter in practice. My plan has always been to share actual test suites for real APIs that I use—but as you can see from the length of time in between posts lately, who knows when that will be.

# What’s Next

This release gets Tasty to a point where I can use it for everything I was previously doing with a handful of shell scripts and curl commands. That said, there’s more ideas in my head as we move toward v1.0:

  • Header validation. This one feels like a good next step. The expect.headers field is already in the struct, just not wired up yet
  • JSON export. The --json flag exists but doesn’t do anything useful; I want test results exportable for CI/CD integration
  • Better error messages. The current output is functional but could be a little easier to parse quickly. This is really just a problem of taste (see what I did there?)

As always, Tasty is something I build for myself that happens to be public. If you’re using it and have thoughts, I’d love to hear them. And if you’re not using it but the idea sounds useful—give it a try!