awspec and the case for testing infrastructure with RSpec
RSpec tests for your AWS resources.
At a glance
- What is it?
- awspec is a Ruby gem that lets you write RSpec examples against AWS resources, with matchers such as should be_running and should have_security_group, a generator that writes specs from resources that already exist, and an endpoint override that points the whole suite at a local mock. The catch is that by default these are assertions about a live, billed account.
- Who is it for?
- Adopt awspec if you already run RSpec and want infrastructure state expressed as expectations, for example that an instance is running and belongs to a given VPC and subnet, in a way that reads like a unit test. Do not adopt it as a policy engine, since it asserts what exists rather than what should exist, and do not run it against a production account without reading what the matchers actually call, because a failure can mean a wrong region rather than a wrong configuration.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 26 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Assertions about a real account, not a mock
awspec is a Ruby gem for writing RSpec tests about AWS resources, and the project credits its lineage precisely: the original idea, code and architecture, comes from Serverspec, and the AWS plus Serverspec concept comes from a separate project by marcy-terui.
That inheritance is visible in the shape of the API. An example is written as a describe block over a resource, and the expectations are matchers:
describe ec2('my-ec2-tag-name') do
it { should be_running }
its(:instance_id) { should eq 'i-ec12345a' }
it { should have_security_group('my-security-group-name') }
it { should belong_to_vpc('my-vpc') }
it { should belong_to_subnet('subnet-1234a567') }
it { should be_disabled_api_termination }
endThe resource is identified the way an operator would identify it, by a tag name in this case, not by an ARN that a provisioning template emitted. The expectations read as statements about the estate, the instance is running, it has this security group, it is in this VPC and subnet, and termination protection is on.
The important thing to understand is what these tests talk to. Unless you change the endpoint, the assertions are queries against a live AWS account, so a spec run needs credentials, costs money in API calls, and describes whatever is actually deployed rather than what a manifest says. That is a different tool from a plan validator, and the value is the same as with any state test, catching drift between the declared architecture and the running one.
The audience is teams already working in RSpec who want that same style applied to cloud resources, and who can accept that the suite touches production infrastructure.
The init step, and the awsecrets handoff
Setting the gem up is a two part job, and the first part depends on whether you are starting fresh.
For a new RSpec project, awspec can generate the init files for you:
$ awspec initFor an existing RSpec project, the same effect is achieved by adding two lines to spec_helper.rb:
require 'awspec'
Awsecrets.load(secrets_path: File.expand_path('./secrets.yml', File.dirname(__FILE__)))The second line is the part worth understanding. awspec does not read credentials itself, it delegates to awsecrets, and the awspec key and secrets precedence follows the awsecrets configuration. That is a deliberate separation, and it has two consequences. You get one place to configure how secrets are found, rather than two competing implementations, and awspec inherits awsecrets' behaviour instead of defining its own, including for a project that already uses awsecrets elsewhere.
The path in the example is relative to the spec_helper.rb file itself, resolved with File.expand_path and File.dirname, which is the right way to write it since a test run's working directory is not guaranteed to be the project root.
The CLI is also where the resource types are documented by example, with a link to the resource types page in the README, and a contributing guide for adding support for a service that is not covered yet.
Credentials come from shared config or a secrets file
There are two documented ways to give the specs credentials, and they mirror how the AWS tooling itself works.
The first is the shared credentials file, configured with the same command the AWS CLI uses:
$ aws configureThe README links to the AWS CLI documentation on configuration settings and precedence, which is the right reference, since the point of this route is that awspec behaves like the tools you already use.
The second is a secrets file in the spec directory, written in YAML:
$ cat <<EOF > spec/secrets.yml
region: ap-northeast-1
aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
EOFThree keys, region and the two credentials, and a file that plainly contains a secret key. That is a deliberate trade in a project aimed at developers, because it makes the test setup self contained and works identically on a laptop and in CI, but it means the file needs to be in your ignore list, and a file that convenient is a file that eventually gets committed. The path is inside spec/ rather than in a config directory, which makes it easy to find and easy to leak.
The combination of the two is that awspec can be pointed at a named profile, which the README shows both for generation and for the test run itself, using the AWS_PROFILE environment variable. That is the route to use when a team has separate accounts for development and production, because it moves the selection out of the repository and into the environment.
Region as a shared context, and identifiers from Terraform
Two details in the documentation make the difference between a suite that works and a suite that lies to you.
The first is per-example region. The README shows a shared context called region that can be attached to a describe block:
describe sqs('my-sqs-queue'), region: 'us-west-2' do
it { should exist }
its(:queue_url) { should eq 'https://sqs.us-west-2.amazonaws.com/xxxxxxxxxxxx/my-sqs-queue' }
its(:queue_arn) { should eq 'arn:aws:sqs:us-west-2:xxxxxxxxxxxx:my-sqs-queue' }
endA resource identifier is often not unique across regions, and a queue URL and an ARN both embed the region. A suite that queries the wrong region does not fail loudly, it fails with a message about a queue that does not exist, or worse, it passes against a same named resource elsewhere. Making the region explicit per example is the difference between an assertion and a coincidence.
The second is identifiers taken from Terraform outputs. The README notes that this is especially useful where resources created by Terraform have the same names, for example when they come from a VPC module, and shows both halves: a Terraform output carrying the instance id, and the spec reading it back with the terraform output command and stripping the result.
output "my_ec2_instance" {
value = aws_instance.my_instance.id
}That pattern is worth copying even if you do not use Terraform everywhere. A tag name is a human label, and human labels are not unique. A provider-assigned id is unique, and the spec then asserts about the object the plan actually created.
Generating a spec from what already exists
Writing every expectation by hand is tedious when the resource already exists, and the generator removes that work:
$ awspec generate ec2 vpc-ab123cde >> spec/ec2_spec.rbThe command names a resource type and a resource, and appends the generated spec to a file. The README reminds you to add require 'spec_helper' to the spec file, which is a small detail with an outsized effect on the debugging experience, since a generated file that does not load the helper will fail in a confusing way.
Note the shell redirection, because it is the sharp edge. The example appends with >>, which is right the first time you generate and wrong the second, when the same file now contains two copies of the same describe block and one of them is out of date. Generation into a fresh file, or with a truncating redirection, avoids the duplicate. The generated file is a starting point, not a finished test, and the value of the exercise is that it turns a running resource into a set of expectations you can then tighten.
The same command takes a profile, so the generated spec describes resources in the account you selected, not the one your shell happens to point at.
Retries, and pointing the suite at a local mock
Two advanced features are documented, and they address the two things that make a live AWS suite painful.
The first is retry behaviour. The ClientWrap class provides mechanisms for retrying AWS API calls when it receives a RequestLimitExceeded error, and it implements a backoff algorithm where the client sleeps for successively longer periods until the calculated backoff is greater than or equal to the backoff_limit, at which point it gives up and re-raises the error. The defaults are configurable in spec_helper.rb:
Awspec.configure do |config|
config.client_backoff 0.0
config.client_backoff_limit 30
config.client_iteration 1
endThrottling is the normal condition of an AWS API, and a suite that fails on the first RequestLimitExceeded looks like a broken test rather than a rate limit.
The second is the endpoint override. Setting the endpoint environment variable tells awspec to use a different endpoint, and the README names the common uses: connecting to AWS through a proxy, using a different service with an AWS compatible interface, and connecting to a local mock AWS environment. The example creates a bucket against a local service with the AWS CLI and an endpoint url, and then runs the suite against the same endpoint:
endpoint=http://localhost:5000 bundle exec rake specThis is the feature that turns the suite from something you run against production into something you can run in CI, and for a team already running against a local mock it is the difference between the project being useful and the project being dangerous.
MIT, tagpr managed releases, and a suite that talks to production
The licence is MIT, with the text in LICENSE.txt at the root of the repository, and the gem is published on RubyGems.
The release tooling is visible in the tree. There is a.tagpr directory entry at the root, next to CHANGELOG.md and the gemspec. The recent tags are v1.36.0 on 2026-02-03, v1.35.0 on 2025-11-10 and v1.34.0 on 2025-10-18, so the release cadence is a minor version every couple of months. The repository is not archived, and the last push was on 2026-09-27, which is well after the newest tag, so the branch and the published gem are at different points, as is normal for a gem that is released when a change is ready rather than on a schedule.
Two operational warnings to carry away from this review. First, the default target of these specs is whatever account your credentials point at, so a misconfigured region or profile makes the suite assert on the wrong estate, and the fix for a failing spec is often to check the region before checking the resource. Second, the tool that reads your credentials is awsecrets, not awspec, so its configuration determines where the keys come from and their precedence, and that is the documentation to read before wiring awspec into an automated pipeline.
The judgement is that awspec is a good fit for a team that already has RSpec habits and a live estate to assert against, and a poor fit for a project that wants to prove a plan is correct without touching AWS.
Editorial conclusion
Adopt awspec if you already run RSpec and want infrastructure state expressed as expectations, for example that an instance is running and belongs to a given VPC and subnet, in a way that reads like a unit test. Do not adopt it as a policy engine, since it asserts what exists rather than what should exist, and do not run it against a production account without reading what the matchers actually call, because a failure can mean a wrong region rather than a wrong configuration. Verify three things first: that the region the specs run against is the region you think, since the README shows a per-example region option, that the credentials in spec/secrets.yml are not committed, and that the endpoint override is in place if you intend to run against a mock rather than live AWS. The last push was on 2026-09-27 and the newest release is v1.36.0 from 2026-02-03.
Frequently asked questions
What is awspec?
A Ruby gem for writing RSpec tests about AWS resources, with matchers such as should be_running, have_security_group and belong_to_vpc. The original idea, code and architecture come from Serverspec, and the AWS plus Serverspec concept comes from a separate project by marcy-terui. The licence is MIT.
How do I install awspec?
Add gem 'awspec' to your application's Gemfile and run bundle, or install it directly with gem install awspec. The command line entry point is awspec, with the init, generate and other subcommands.
How do I set up a fresh RSpec project to use awspec?
Run awspec init to generate the init files. In an existing project, add require 'awspec' and Awsecrets.load(secrets_path: File.expand_path('./secrets.yml', File.dirname(__FILE__))) to spec_helper.rb, since awspec delegates credential handling to awsecrets.
Where do the AWS credentials for awspec come from?
Either from shared credentials configured with aws configure, or from a spec/secrets.yml file containing region, aws_access_key_id and aws_secret_access_key. The key and secrets precedence follows the awsecrets configuration, and named profiles can be selected with the AWS_PROFILE environment variable.
Can awspec run against a local mock instead of real AWS?
Yes. Setting the endpoint environment variable makes awspec use a different endpoint, for example endpoint=http://localhost:5000 bundle exec rake spec, which is the documented route for connecting to a local mock AWS environment.
How do I run the awspec suite and generate specs?
Run bundle exec rake spec, with the rake gem added to the Gemfile for a blank project. To generate a spec from a resource that already exists, use awspec generate ec2 vpc-ab123cde >> spec/ec2_spec.rb, and remember to add require 'spec_helper' to the generated file. The newest release is v1.36.0 from 2026-02-03 and the last push was on 2026-09-27.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/k1low-awspec)Was this useful?