Combustion and the case for testing a Rails engine without a full app
Simple, elegant testing for Rails Engines
At a glance
- What is it?
- Combustion builds a minimal Rails application inside your engine's spec directory and loads only the parts of the framework your engine needs, so engine specs run in a real Rails context without a dummy app. The trade is that the generated internal app becomes a surface you have to keep correct.
- Who is it for?
- Combustion suits a team maintaining a Rails engine that wants its specs to exercise real Rails behaviour, controllers, routes and ActiveRecord without maintaining a separate dummy application, since the internal app is generated and pruned to whatever the engine actually touches.
- 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 3 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 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The alternative this replaces: a dummy app in your spec folder
Combustion is a library to help you test your Rails engines in a simple and effective manner, instead of creating a full Rails application in your spec or test folder. That sentence names the problem exactly.
The conventional way to test a Rails engine is to keep a dummy application alongside it, with its own Gemfile, its own config, its own routes and its own database configuration, and to point the test suite at that. The dummy app drifts. It gets out of date with the engine, it accumulates settings nobody remembers adding, and it is a second thing to review when something breaks.
Combustion replaces that with an internal application that it builds for you inside the engine, at spec/internal by default, and the key word in its own description is only. The project guide says you only need to add the files within that directory that you are going to use. So the internal app is not a full application you maintain, it is a scaffold that grows only where your engine requires it.
What you get in return is that your specs run within the context of your engine, using only the parts of a Rails app you need, and the surrounding project uses a normal Gemfile and a normal spec_helper rather than a parallel application.
The project is MIT licensed, written in Ruby, and its default branch is main.
The gem, the spec_helper and one generator
The setup has three moving parts, and the order of the first two matters.
The gem goes into either your gemspec or your Gemfile, depending on how you manage the engine's dependencies:
# gemspec
gem.add_development_dependency 'combustion', '~> 1.3'
# Gemfile
gem 'combustion', '~> 1.3'Then in your spec_helper.rb you get Combustion to set itself up, and the guide is explicit that this has to happen before you introduce rspec/rails and, if being used, capybara/rails:
require 'bundler'
Bundler.require :default, :development
# If you're using all parts of Rails:
Combustion.initialize! :all
# Or, load just what you need:
# Combustion.initialize! :active_record, :action_controller
require 'rspec/rails'
# If you're using Capybara:
# require 'capybara/rails'
RSpec.configure do |config|
config.use_transactional_fixtures = true
endThe load order is the part that bites. Combustion has to build the application before the RSpec Rails helpers attach to it, so moving the initialize! call after the requires will not work.
Third, you run a generator in the directory of your engine to create a minimal set of files expected by Rails:
combust
# or, if bundling with the git repo:
bundle exec combustThat creates spec/internal with the skeleton Rails wants to find, including a config.ru for running the environment with Rack.
Choosing which Rails modules to load is the main decision
By default, Combustion does not come with any of the Rails stack, and you customise it by passing what you would like loaded to the initialize! call:
Combustion.initialize! :active_record, :action_controller,
:action_view, :sprocketsAnd then the engine's Gemfile has to contain the matching gems:
group :test do
gem 'activerecord'
gem 'actionpack' # action_controller, action_view
gem 'sprockets'
endThe guide says to make sure to specify the appropriate version that you want to use, and to note that ActiveSupport and Railties are always loaded, as they are an integral part of Rails.
The all shortcut exists for people who do not want to make that decision. The guide warns about it in a specific and useful way: using :all loads all key parts of Rails that are considered essential for that version. It gives two examples of what that means. ActiveJob is only loaded for Rails 4.2 onwards, and Sprockets is only loaded for Rails 3.1 through 6.1, since it is no longer part of the default set in 7.0.
So :all is version aware, not a blunt hammer, but it is still all. The explicit list is better for an engine that only touches one or two parts of the framework, because the test suite then proves the engine works with a small, defined surface.
One more thing the guide handles: if you want the app somewhere other than spec/internal, set Combustion.path before calling initialize!.
ActiveRecord needs a database config and a schema file
If you are using ActiveRecord, there are two critical files within the internal Rails app that you will need to modify, and the guide names them: config/database.yml and db/schema.rb.
Both follow the same structure as in any normal Rails application, and the schema file lets you avoid migrations, since it gets run whenever the test suite starts:
ActiveRecord::Schema.define do
create_table(:pages, :force => true) do |t|
t.string :name
t.text :content
t.timestamps
end
endThe comment in the guide about the tables being overwritten if they already exist is not incidental. It is necessary, because the schema is reloaded on every test run rather than migrated incrementally, which is the point of using a schema file instead of migrations in an engine that does not own the models.
Models that the engine does not provide are looked up at spec/internal/app/models, which is where you would put a plain ActiveRecord model for the test to use.
If you would rather prepare the database yourself, three flags let you turn parts of the setup off: :database_reset, :load_schema and :database_migrate, all of which default to true:
Combustion.initialize! :active_record,
:database_reset => false,
:load_schema => falseThere is also a route for SQL fixtures instead of a schema. Name the file structure.sql and set the schema format before initialising:
Combustion.schema_format = :sql
Combustion.initialize! :allControllers, views and routes live in the internal app too
Testing an engine's controllers and views means giving them somewhere to live inside the internal application.
The guide is careful about the wording here. You will only need to add controllers and views to your internal Rails app for whatever you are testing that your engine does not provide. This may be nothing at all, so perhaps you do not even need the spec/internal/app/views or spec/internal/app/controllers directories.
That is the whole point of the approach stated again in a different context. An engine that provides its own controllers needs no internal ones, and the guide is signalling that an empty internal app is a valid state rather than a setup mistake.
What you do need, if you are testing controllers or views, is routes. You modify spec/internal/config/routes.rb accordingly:
Rails.application.routes.draw do
resources :pages
endAnd the guide notes that just like in a standard Rails app, a mounted engine's routes are accessible through whatever it has been loaded as.
Rails application settings can be set without creating an environment file at all, by passing a block to initialize!:
Combustion.initialize! :all do
config.active_record.whitelist_attributes = false
endThe precedence is spelled out. Values given through the initialize! block are set during the Rails initialization process, exactly before the corresponding environment file inside the internal app's config/environments directory is loaded, when that file exists, overriding Combustion's defaults. Parameters defined in an environment file such as test.rb would override both Combustion's defaults and the settings passed to initialize!.
The known friction: other gems that require Rails on load
There is a specific class of problem that the guide flags, and it is worth understanding before you commit to the tool.
Other gems may require parts of Rails when they are loaded, and this could cause some issues with Combustion's own setup. The guide's advice is to manage the loading yourself by setting :require to false in the Gemfile for the gem in question, and then requiring it manually in your spec_helper. It points at issue #33 for an example with FactoryBot.
This is a direct consequence of the load-order rule from the setup section. Combustion builds the application, then rspec/rails and capybara/rails attach to it, and a gem that reaches for a Rails component at require time may do so before Combustion has put one in place, or may pull in a part of the framework that Combustion deliberately did not load.
The manual-require workaround trades one problem for a slightly less convenient Gemfile. It is the kind of friction that a project with a small, well-behaved dependency set will never meet and a project with a large one will meet repeatedly, so the number of gems that pull in Rails is a reasonable predictor of how Combustion will go for you.
Two smaller notes round out the operational detail. Tests execute within the test environment for the internal Rails app, so logs are available at spec/internal/log/test.log, and you should probably create that log directory so Rails does not complain. And Minitest support is considered experimental, but it is certainly working for some, with comments on others' experiences welcome.
Running the test app and the project shape
Once the setup is in place, the test environment can be started with Rack. A config.ru file is provided by the generator, so you just run rackup and visit http://localhost:9292.
That is a small feature with a large effect on the debugging loop. The engine's own controllers are mounted into this internal application, so a request that fails in a spec can be reproduced in a browser against a running server, with the same routes, the same middleware and the same environment. For a gem that people embed in other applications, being able to poke at it in a browser is worth more than it sounds.
The project layout is a small, conventional gem. The top level holds combustion.gemspec, a Gemfile, a Rakefile, an Appraisals file, HISTORY, LICENCE, a Procfile.support file, and an AI_POLICY.md, alongside the lib/, exe/, spec/, bin/ and templates/ directories. The templates/ directory is what the generator uses to lay down spec/internal, and exe/ holds the combust executable.
There is also a .rubocop.yml and a .rspec file, so linting and RSpec options are configured in the repository rather than left to the host project. The last push to the repository was on 2026-10-20, so the work is current, and the project publishes no GitHub releases, so the gemspec version is the only version reference available to a user.
Editorial conclusion
Combustion suits a team maintaining a Rails engine that wants its specs to exercise real Rails behaviour, controllers, routes and ActiveRecord without maintaining a separate dummy application, since the internal app is generated and pruned to whatever the engine actually touches. It does not suit a project that wants the framework loaded wholesale with no decisions to make, because the module list is explicit by design, and it does not suit a team that needs a full application shell for other reasons. Before adopting it, read the section on other Rails-focused gems, because a gem that requires Rails on load can conflict with the setup, and note that the last push was on 2026-10-20 with the project publishing no GitHub releases.
Frequently asked questions
What does Combustion do for Rails engine testing?
It sets up a Rails application at spec/internal and loads only the parts of the framework your engine needs, so you can write specs in the context of your engine without creating a full Rails application in your spec folder.
How do I set up Combustion in a spec_helper?
Add the gem to your gemspec or Gemfile, call Combustion.initialize! with the modules you want, and do it before requiring rspec/rails and capybara/rails. Then run the combust generator in your engine directory to create the minimal files Rails expects.
Which Rails modules does Combustion load by default?
By default none of the Rails stack. You pass what you want to the initialize! call, and ActiveSupport and Railties are always loaded because they are an integral part of Rails. The :all argument loads all key parts of Rails considered essential for the version in use.
How do I use a SQL file instead of schema.rb in Combustion?
Name the file structure.sql and set Combustion.schema_format = :sql before calling Combustion.initialize! :all. Without that setting, the schema file at spec/internal/db/schema.rb is run whenever the test suite starts, which avoids needing migrations.
What is the known issue with FactoryBot and Combustion?
Other gems may require parts of Rails when they are loaded, which can conflict with Combustion's own setup. The guidance is to set :require to false for that gem in your Gemfile and require it manually in your spec_helper, with issue #33 given as a worked example.
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/pat-combustion)Was this useful?