Testing Sinatra Applications inside Rails - ruby-on-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.

Related

Debugging in single file

Sometimes, I want to debug and test my rails code in a single file, instead of running a full rails app.
Is there a way to add the rails methods to a single file for debugging?
For example, I wanted to test and play with the delegate_missing_to method, but can't without running inside my actual rails app.
For testing ActiveRecord, I can use require 'active_record', which works really well, but I don't have access to other methods.
Any solution?
Check out bug reporting test cases from rails.
https://guides.rubyonrails.org/contributing_to_ruby_on_rails.html#create-an-executable-test-case
These are basically rails app in a single file.

Automatic Reloading of Sinatra App mounted inside Rails App

I've run into what I hope is an easy-ish fix for someone more experienced than me when adding a Sinatra app to an existing Rails app.
I currently have a large rails monolith that I am planning to break apart into an SPA backed by a JSON API. Since I will still need to support the monolith until the API is completed I would like to mount the API (written in Sinatra) inside the existing Rails app as I port functionality over with a goal of removing the Rails app itself in a few months. The reason I've mounted the Sinatra app inside Rails instead of setting it up as a separate service was that I wanted easy code sharing between the two as I intend to continue using ActiveRecord as my ORM in Sinatra once the migration is complete.
I've mounted the Sinatra mockup inside the Rails app without any issues using the Rails routes.rb file as:
Rails.application.routes.draw do
mount API::Core, at: '/api'
...
end
I'm not doing any work with config.ru as mounting the API inside the routes.rb file seemed to suit my needs. If I need to put in some more legwork to get this running properly that is not an issue.
The entrypoint for the sinatra app is also fairly simple with just a couple controllers loaded in to segment the routing:
require 'sinatra/base'
require 'sinatra/json'
require_relative 'controllers/first_controller'
require_relative 'controllers/second_controller'
module API
class Core < Sinatra::Base
register Sinatra::FirstControllerApi
register Sinatra::SecondControllerApi
end
end
The problem I'm running into is one I expected with Sinatra but haven't been able to flex my google-fu enough to find a solution. Rails automatically reloads code on each change/request as expected but Sinatra does not. Every time I change controller code in the Sinatra API I need to restart the entire Rails server to serve the new content. Since my dev environment runs in docker containers this can take a while to start up each time and is becoming cumbersome.
Is there a 'canonical' solution to the problem of automagically reloading the Sinatra app mounted inside the Rails app or am I just over-complicating the problem? I know there are some gems for Sinatra that apply to this space but I haven't seen any info in how to get them working in this 'odd' edge case.
Please let me know if you need any more info, hopefully I've provided enough for someone to comprehend my issues.
Just like with a Sinatra app on it's own, a Sinatra app mounted inside o rails will not automatically reload as you make changes. Typically in Sinatra people would use external tools to help solve this problem, so they might set up something like Shotgun to watch the directory and restart the server any time a change has happened.
But, like most things in Rails, there is a way hook into it's functionatly for your own programming benefit.
ActiveSupport::FileUpdateChecker
Rails has a handy ActiveSupport::FileUpdateChecker that can watch a file, or a list of files. When they change it will call back to a block. You can use this to reload the Sinatra app -- or anything else for that matter.
In your case you might want to add the following to config/environments/development.rb in the config block for the application:
Rails.application.configure do
# ...
sinatra_reloader = ActiveSupport::FileUpdateChecker.new(Dir["path/to/sinatra/app/**"]) do
Rails.application.reload_routes!
end
config.to_prepare do
sinatra_reloader.execute_if_updated
end
end
Robert Mosolgo has a good write up on his blog: Watching Files During Rails Development. His approach is a little more thorough, but more complicated.

Run controller test on two different Rails applications

I'm helping to migrate a Rails 2 app to Rails 4, and I have come in towards the end of the project. The Rails 2 app has no tests.
Obviously we need tests to make sure the Rails 4 app works, but we don't want to write the tests in Rails 2 syntax, and then go through the pain of migrating the tests to Rails 4. We currently use RSpec on other projects.
I thought of writing them in Rails 4 as feature tests, and using Capybara.app_host to re-route the requests to a running Rails 2 application instance. Problem is, that the application is an API, and Capybara is not meant for testing APIs.
Is there any way of routing RSpec controller tests to another server instance?
Thanks again for the comments.
I think we have a plan. We tried to get cucumber and cucumber-api-steps running on our old project, but we realised that it was going to be hard work getting old gem versions to work nicely together.
The solution I'm trying now is using RSpec 1.3 and creating a spec/requests folder and adding controller tests to it.
I got this idea from a post from http://matthewlehner.net/rails-api-testing-guidelines/ - thanks Matthew. I don't see request specs in the docs until RSpec 2, so we make our controller tests pretty close to integration tests using integrate_views, then check the contents of the body returned by the controller.
As for the syntax, maybe we can include an expect(..) helper to return an object that calls #should from a #to method in the Rails 2 project and remove it when we port the Rails 4 project.

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$})

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

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.

Resources