How to test a Rails engine that depends on its hosting application? - ruby-on-rails

I have a rails application, and I'm trying to split the code into several engines.
The main application Holds one main controller - Api::ApplicationController, and all the controllers in the engines inherit from that controller, for example:
Api::Products::ProductsController < Api::ApplicationController
I use RSpec for testing.
When I run the application everything works just perfect, but when I try to run the engine's Rspecs, I get an error:
uninitialized constant Api::ApplicationController
As you understand the engine cannot work separately from the application, so I tried to stub Api::ApplicationController inside the spec (products_controller_spec.rb), but it fails before it even starts to run the spec.
I thought maybe I should Implement it in a different way, and inject ApplicationController functionality somewhere instead of inherit from it, but I don't know where.
Another thing I considered, is to inject the engine's specs into the hosting application and run it from there, but it doesn't seem to work, and it is not possible to debug the specs that way.
How can I test it?

Rails Engines create small dummy applications for testing purposes. You need to create a minimal application that has the features you need to test your application, and it will use this to run it's specs.
See here:
http://edgeguides.rubyonrails.org/engines.html#testing-an-engine
A better solution for this case I think is to put shared application code that all the engines share as modules in a shared gem that all your engines can include.

Related

How to tell RSpec to restart Rails before an example is run?

Is it possible to tell rspec to restart Rails before an example is run? I'm building an Engine that hooks into the Rails initialization process and the users can make some configuration changes, in an initializer, that impact how Rails and the Engine are configured. I want to be able to simulate those configuration changes, restart rails and test the result.
I haven't done this feat yet, but as best practice I think your engine tests should be part of the engine and should have minimal dependencies.
Some approaches I've seen and believe you should try and combine:
Mock a minimal parent rails app to test your engine.
Write multiple dummy apps to test with.
Instead of loading the entire rails application, you can split spec_helper and rails_helper in smaller parts, also gaining in setup time.
You can write custom rake tasks to switch environment before spawning a new test thread.
You can also overwrite at runtime the configuration values which reflect in your test (plus: use dependency injection!).
If your initializer is complex enough, you could extract it in a testable helper and wire it up in your test initializers.
Also, there seems to be a gem for that: Combustion.

Rspec: run an outside rails server

This question is about starting a rails server of the external project from a rspec environment.
There is 2 projects.
First project act as the Admin Back Office, it's the central application where users interact with web pages. I call it BackOffice
Second project is a Json API Server which will receive commands from the Admin Back Office through json requests.I call it ApiServer
I am trying to test API interaction between those 2 rails projects, and I would like to set-up rspec so I can write and maintain my spec files in BackOffice project. Those specs would start a ApiServer rails server and then play around to perform the tests.
My issue is about starting the ApiServer rails server. After looking at the rails app initialization files, I assumed I had to add a require to "config/environment".
But when I insert into BackOffice/spec/spec_helper.rb
require File.expand_path('../../../ApiServer/config/environment', __FILE__)
I get the error
`initialize!': Application has been already initialized. (RuntimeError)
# Backtrace to the file:
# ApiServer/config/environment.rb
# Line:
# Rails.application.initialize!
I also tried to simply call the following in backticks
`cd /api/path; bundle exec rails s -p 3002`
but got the same kind of error
Then I got inspiration from Capybara source code, and required the "ApiServer/application", then I am able to create a ApiServer.new object, but as soon as I call initialize! on it it I get the same message.
Any help is greatly appreciated. Cheers
Actually the second app is nothing more then an external service, which is better to stub for the tests.
There is one nice article from thoughtbot about using vcr gem to mock external web services:
https://robots.thoughtbot.com/how-to-stub-external-services-in-tests
Obligatory "don't do that unless you really need to".
However, since it seems you know what you need:
Short answer:
You need to isolate both application in system environment and launch it from there using system-calls syntax.
Long answer:
What you're trying to do is to run two Rails applications in the same environment. Since they both are Rails applications they share a lot of common names. Running them ends in name clash, which you're experiencing. Your hunch to try simple back ticks was good one, unfortunately you went with a bundler in already existing environment, which also clashes.
What you have to do in order to make it work is to properly isolate (in terms of code, not in terms of network i.e. communication layer ) application and then run launcher from rspec. There are multiple ways, you could:
Use Ruby process control (Check this graph, you could try to combine it with system level exec)
Daemonize from Operating System level (init.d etc.)
Encapsulate in VM or one of the wrappers (Virtualbox, Vagrant, etc.)
Go crazy and put code on separate machine and control it remotely (Puppet, Ansible, etc.)
Once there, you can simply run launcher (e.g. daemon init script or spawn new process in isolated environment) from RSpec and that's it.
Choosing which way to go with is highly dependent on your environment.
Do you run OSX, Linux, Windows? Are you using Docker? Do you manage Ruby libraries through things like RVM? Things like this.
Generally it's a bad idea to require launching another service/application to get your unit tests to pass. This type of interaction is usually tested by mocking or vcring responses, or by creating environment tests that run against deployed servers. Launching another server is outside the scope of rspec and generally, as you've discovered, will cause a lot of headaches to setup and maintain.
However, if you're going to have these rails projects tightly coupled and you want them to share resources, I'd suggest investigating Rails Engines. To do this will require a substantial amount of work but the benefits can be quite high as the code will share a repository and have access to each other's capabilities, while maintaining application isolation.
Engines effectively create a rails application within another rails application. Each application has it's own namespace and a few isolating guards in place to prevent cross app contamination. If you have many engines it becomes ideal to have a shell rails application with minimal capabilities serving each engine on a different route/namespace.
First you need to create housing for the new api engine.
$ rails plugin new apiserver --mountable
This will provide you with lib/apiserver/engine.rb as well as all the other scaffolding you'll need to run your API as an engine. You'll also notice that config/routes.rb now has a route for your engine. You can copy your existing routes into this to provide a route path for your engine. All of your existing models will need to be moved into the namespace and you'll need to migrate any associated tables to the new naming convention. You'll also have some custom changes depending on your application and what you need to copy over to the engine, however the rails guide walks your through these changes (I won't enumerate all of them here).
It took a coworker about a week of work to get a complicated engine copied into another complicated rails server while development on both apps was occurring and with preserving version control history. A simpler app -- like an api only service -- I imagine would be quicker to establish.
What this gives you is another namespace scope at the application root. You can change this configuration around as you add more engines and shared code to match various other directory structures that make more sense.
app
models
...
apiserver
app
...
And once you've moved your code into the engine, you can test against your engine routers:
require "rails_helper"
describe APIServer::UsersController do
routes { APIServer::Engine.routes }
it "routes to the list of all users" do
expect(:get => users_path).
to route_to(:controller => "apiserver/users", :action => "index")
end
end
You should be able to mix and match routes from both services and get cross-application testing done without launching a separate Rails app and without requiring an integration environment for your specs to pass.
Task rabbit has a great blog on how to enginize a rails application as a reference. They dive into the what to-do's and what not-to-do's in enginizing and go into more depth than can be easily transcribed to a SO post. I'd suggest following their procedure for engine decision making, though it's certainly not required to successfully enginize your api server.
You can stub requests like:
stub_request(:get, %r{^#{ENV.fetch("BASE_URL")}/assets/email-.+\.css$})

What testing tools and methods did Corey Haines use at GoGaRuCo 2011?

In this video from GoGaRuCo 2011, Corey Haines shows some techniques for making Rails test suites much faster. I would summarize it as follows:
Put as much of your code as possible outside the Rails app, into other modules and classes
Test those separately, without the overhead of loading up Rails
Use them from within your Rails app
There were a couple of things I didn't understand, though.
He alternates between running tests with rspec and spn or spna (for example, at about 3:50). Is spn a commonly-known tool?
In his tests for non-Rails classes and modules, he includes the module or class being tested, but I don't see him including anything like spec_helper. How does he have Rspec available?
Sorry about the confusion. spn and spna are aliases I have that add my non-rails code to rspec's load path. There isn't anything special about them, other than adding a -I path_to_code on the command-line.
These days, I add something like this to my .rspec file:
-I app/mercury_app
Then I can do simple require 'object_name' at the top of my specs.
As for not including spec_helper: that is true, I don't. When you execute your spec file with rspec <path_to_spec_file>, it gets interpreted, so you don't need to require rspec explicitly.
For my db specs these days, I also have built an active_record_spec_helper which requires active_record, establishes a connection to the test database, and sets up database_cleaner; this allows me to simply require my model at the top of my spec file. This way, I can test the AR code against the db without having to load up my whole app.
A client I am working at where we are using these techniques is interested in supporting some blog posts about this, so hopefully they will start coming out towards the middle of June.

Reusable part of application

I want to create a sms payment engine and reuse it in several applications. It would be best to be able to just copy/paste one directory, maybe configure some minor stuff and just have it working (with views, controllers, etc.).
What's the best way to do this? Of course I'm not asking about this sms thing but about the way to create an isolated piece of application. It's something like a helper application inside of the major application.
There a three ways to build Rails extensions : plain-old ruby code, Railties and Engines.
Railties and Engines allow you to interact with the Rails framework during the initialization using hooks and therefore extend Rails. Actually, every major Rails component (ActiveRecord, ActionPack, etc.) is a Railtie.
The main difference between a railtie and a Rails engine is that an engine can define its own configuration, models, controllers and views. In a way, an engine is a Rails application you can deploy in another one. In your case, I guess a Rails Engine would be the right choice.
Whatever the option you use, you will have to build a gem to distribute your extension and share it across projects.
Here is a gist explaining both the Railtie and Engine concepts
A guide to starting your own rails engine.
Enginex, a command line tool which creates a Rails 3 Engine
I guess the best way to reuse your code is putting them to a gem, then install that gem.
I think the best way to extract reusable part of your application is to create a RubyGem. You can find a tutorial about creating RubyGems here. And there is a Jeweler, a very nice tool to create RubyGems. More about Jeweler, you can find here

Testing Sinatra Applications inside Rails

I've created a simple application to assist me in learning Rails.
Because I like a challenge, I've also started to build, inside this application, a Sinatra app (to handle API calls without the overhead of the full Rails stack).
I've come to the point where I want to start testing both applications before I move any further, but I'm not sure where to write the tests for my Sinatra app. It's presently located under lib/api - should I create a tests folder underneath that, or use the main Rails test folder at the root?
You can test this sinatra app in request of rspec by example or by integration test.
In this part you just need define which url you want request and see the result.

Resources