Rails: Writing Engine Tests That Depend on Main Application Models
When developing a new Ruby on Rails project, one doesn't always start from scratch. Often one first searches for an open-source project that will at least get one part of the way there. For example, one might want to do something as simple a create a website with a blog as well as image galleries. The first step in this journey might be to find some open-source blog software for Rails, e.g. Publify (previously called Typo). Great, one has a blog!
Now what about that image gallery stuff? One would surely like to utilize the same authentication/account structure that is in use in the blogging software, but there's a problem: Projects such as Publify are distributed as an entire Rails application. Yes, it's of course open-source, but it was written to be one's entire website. Adding image galleries means one needs to figure out and alter the Publify source code, and that's just not cool.
The Solution: Mountable Engines
Actually, that exact situation is what led to my writing Proclaim. I wanted something that didn't try to be my entire website, and just focused on doing what it was supposed to do: provide a blog. It was written as a mountable engine, and it doesn't even include users or authentication-- it will make use of whatever the main application is using.
The Resulting Difficulties
That desire quickly led to a testing issue, though. The blog Post model doesn't need to know anything about its author, and the engine doesn't need to deal with accounts in any way, but it makes sense for posts to have an author, so the solution is to make the class for the author configurable and we say that the Post model belongs_to it. Then the main application can configure that author class and the engine will then use whatever the main application configured. However, how does one write tests for the Post model if the engine doesn't contain a model for the author?
Let me answer that with a quick tutorial (see the official Rails Engines guide for more information).
Create Engine with Posts
First, create your mountable engine:
$ cd ~
$ rails plugin new blorgh --mountable
In this tutorial, all we care about is the cross-over between engine and main application, and for our engine that happens on the Post model (we don't need any comments or anything). So create a scaffold for your Post model:
$ cd ~/blorgh/
$ rails g scaffold post author_id:integer title:string text:text
$ rake db:migrate
Notice the author_id bit. That's where the Post will belong_to an author, even though the author class itself is not defined within the engine. Let's add that belongs_to logic to the Post model now:
class Post < ActiveRecord::Base
belongs_to :author, required: true, class_name: "User"
This is saying that the Post model must belong to an author, which happens to be a model called User (no, such a model doesn't exist).
Alright, we have a Post model, so before we start fleshing it out, let's write some tests (which is really what I'm writing about anyway). We know that a Post should have an author, so let's write a test to ensure that a Post cannot be saved if it doesn't have an author. The scaffold generated a blorgh/test/models/blorgh/post_test.rb file for us, so we might as well use that:
class PostTest < ActiveSupport::TestCase
test "ensure author is required" do
post = Post.new(title: "Foo", text: "Bar", author_id: nil)
refute post.save, "Post should require an author_id"
# Author with 12345 shouldn't exist
post = Post.new(title: "Foo", text: "Bar", author_id: 12345)
refute post.save, "Post should require a valid author"
Pretty simple, right?
First Test Run
Alright, time to run the tests:
$ cd ~/blorgh/
$ rake test
NameError: uninitialized constant Blorgh::Post::User
Right, so: the Post model belongs_to a User model, but the User model doesn't exist. Where should it go? We've already decided that it doesn't belong in our engine. Fortunately, Rails is way ahead of you.
The Dummy Application
Your engine has to be mounted before it can do anything, including run tests. For this purpose, there's an entire Rails application pre-generated for you in blorgh/test/dummy/. This is a perfect place for the User model to go:
$ cd ~/blorgh/test/dummy
$ rails g model User name:string
Now run the dummy application's migrations:
$ cd ~/blorgh/
$ rake db:migrate RAILS_ENV=test
You should notice the "CreateUsers" migration running.
Second Test Run
Alright, let's try our test again:
$ cd ~/blorgh/
$ rake test
<snip> 0 failures, 0 errors, 0 skips
The use of the dummy application took some getting used to, and I don't feel like it was well-explained in the documentation. I was initially under the impression that, if I wrote models in the dummy application, I would also have to write tests in the dummy application, and that just seemed nasty. Comments in the Rails mailing list about how the dummy application shouldn't be committed to version control didn't help-- I obviously disagree with that.
In the end, you'll find that the dummy application is a perfect fit for testing the crossover between your engine and the main application. The test written in this tutorial didn't use any fixtures or factories for simplicity, but note: if you use FactoryGirl, don't put factories in the dummy application (FactoryGirl won't look there). Put factories for models defined in the dummy (e.g. the User model) in blorgh/test/factories/ (not blorgh/test/factories/blorgh/, since it isn't contained within the engine).