Unit Testing in Rails
Tags: rails, testing, ruby
Published: 2019-10-17 19:37:33

For my blog this week, I decided to explore the topic of unit testing in Rails. Throughout my time at the Flatiron School, I completed labs that included [RSpec](https://en.wikipedia.org/wiki/RSpec) tests to determine if the code was correct. Although I indirectly used hundreds of these RSpec tests, I never quite dove into the topic of writing tests myself. I was always curious how to write my own tests and knew it is an extremely skill to learn for Test-Driven Development (TDD), so I decided I needed to teach myself how to write them! 

![Eat, Sleep, Code, Unit Test](https://www.typemock.com/wp-content/uploads/2018/08/The-Business-Case-for-Unit-Testing-part2.png)

##Unit Testing

I wanted to begin my testing journey with the concept of [unit testing](https://en.wikipedia.org/wiki/Unit_testing). Broadly speaking, unit testing is a testing method where individual units of source code, program modules, usage procedures, and operating procedures are tested to determine if they are fit for use. They make sure that a section of an application, or a “unit”, is behaving as intended. In a [Rails context](https://guides.rubyonrails.org/v3.2/testing.html#unit-testing-your-models), unit tests are what you use to test your models. Although it is possible in Rails to run all tests simultaneously, each unit test case should be tested independently to isolate issues that may arise. The goal of unit testing is to isolate each part of the program and show that the individual parts are correct.

###Advantages of Unit Testing
Unit testing has become a standard in many development methods because it encourages well-structured code and good development practices. [One advantage](https://en.wikipedia.org/wiki/Unit_testing#Advantages) of unit testing is that it finds problems early in the development cycle rather than later. It’s always better to find bugs earlier so that features that are added on later in the cycle are using the correct component parts. Additionally, unit testing forces developers to structure functions and objects in better ways, since poorly written code can be impossible to test. Finally, unit testing provides a living documentation of the system as it is developed. The description of the tests can give an outsider a basic understanding of the unit’s API and its features. 


##The Test Directory  
In a normal situation, when creating a new Rails app, you would run the `rails new application_name` command after installing Rails. Once the skeleton of the new Rails application has been built, you will see a folder called `test` in the root of the new application, and many subfolders and files within the `test` directory. For the purposes of this post, I will be focusing on adding tests to the `models` directory, since unit testing in Rails primarily involves testing models. However, if you are interested in learning about the other directories within the "test" folder (such as `helpers`, `channels`, `controllers`, etc), you can do so [here](https://guides.rubyonrails.org/testing.html#rails-sets-up-for-testing-from-the-word-go).

Rails automatically adds new test files to the `models` directory when new models are generated. So if you were to run a `rails generate model User`, Rails would add a `user_test.rb` file in the `test/models` directory. The unit tests will be written in these files for each model. 


##Unit Testing My Project
I decided a good place to start practicing unit testing would be my project called EffectiveDonate, a nonprofit donation website. I originally developed this project without any tests, and it has a fairly complicated Rails backend with many models, so there was a plethora of features I could test. 

It was slightly more work to add in the tests at this point, since I initially ran the `rails new` command without adding tests. I ended up just creating a new blank project with the test directory, and then dragging it into the root of my EffectiveDonate backend. Then I added files for the models that I wanted to test into the `models` directory, such as `project_test.rb`. 

To learn how to unit test my models, I followed the **[Rails Guides for Unit Testing Models](https://guides.rubyonrails.org/v3.2/testing.html#unit-testing-your-models)**. I won't repeat all the information I read there, but I would highly recommend reading it since it is an excellent resource for how to write Rails testing code, testing terminology, how to prepare your app for testing, running tests, and test assertions. An important piece of advice in the documentation is to include a test for everything which could possibly break. And that it is a best practice to have at least one test for each of your validations and at least one test for every method in your model. So with that framework, I began writing tests!

###Project Model
The most complex model in my backend for EffectiveDonate is the `Project` model. This model has several has_many / belongs_to associations with other models, and the primary call to the external API (GlobalGiving) that feeds all the data in my project happens in this model. 

But as I began writing tests for the `Project` model, I realized I didn't have any validations! So theoretically it would be possible for a `Project` instance to be created without having key attributes like a title or images. While GlobalGiving's API generally has very clean data, I want to make sure all the `Project` data in my app has the attributes I need. So I went ahead and wrote the following validations:
``` ruby
  validates :title, presence: true
  validates :image_url, presence: true
  validates :project_link, presence: true
  validates :theme_str_id, presence: true
```
These will ensure that `Project`s without these attributes will not be saved. 

Now it was time to test these validations. In my `test/models/project_test.rb` file, I had the following class set up:
``` ruby
require 'test_helper'

class ProjectTest < ActiveSupport::TestCase
  test "the truth" do
    assert true
  end
end 
```
This is the skeleton that Rails adds for each model that is generated. The "the truth" test is an example test that will always pass and allows you to verify that your Test class is up and running. In Rails testing, assertions are the most important part of the test. They actually perform the checks to make sure things are going as planned. The example test above will pass because it just ensures that the argument in the assertion (`true`) is true. There are [many other types of assertions](https://guides.rubyonrails.org/v3.2/testing.html#assertions-available) that you get for free with Rails unit tests, each with their own purpose. 

In order to test my new `Project` validations, I wrote similar tests for each validation. Each test created a new `Project`, then assigned the projects all of the other required attributes, except for the one that I am testing for. Then I wrote an `assert_not` to ensure that the `Project` would not save without the missing attribute. 

For example, here is my validation for my test to ensure a `Project` will not save without a title: 

``` ruby
test "should not save a project without a title" do
    project = Project.new
    project.image_url = "https://files.globalgiving.org/pfil/34796/pict_large.jpg?m=1534281939000"
    project.theme_str_id = "env"
    project.project_link = "https://www.globalgiving.org/projects/nourish-a-young-brain-protect-one-ancient-culture/"
    assert_not project.save, "Saved the project without a title"
  end
```
If I ran the test and the new `project` instance did get saved, I would know there was a problem with my validation, and would receive the message in the argument of the assertion: "Saved the project without a title". 

To test the other methods in my `Project` model, I wrote different tests. These methods perform the fetches from the GlobalGiving API, and return JSONs with the project data. I used the `assert_nothing_raised` assertion to ensure my `queryActiveProjects` method runs without raising an error (this will ensure the API endpoint and my credentials are correct):

``` ruby
  test "should query the active projects API endpoint without error" do
    assert_nothing_raised do
      Project.queryActiveProjects(nextProject:nil)
    end
  end
```

I also wanted to ensure that the JSON returned by this method has attributes that the application needs, such as `nextProjectId`, and that the JSON includes a `project` object with a valid `id`:

``` ruby
    test "should return a json of projects that has a next project ID" do
      assert Project.queryActiveProjects(nextProject:nil)["projects"]["nextProjectId"]
    end

    test "should return a json of projects whose first project has an ID" do
      assert Project.queryActiveProjects(nextProject:nil)["projects"]["project"].first["id"]
    end
```
These tests use `assert` to validate that both of these attributes exist.


###Country Model
My `Country` model includes similar API fetch methods to `Project` above, so I wrote a similar unit test using `assert_nothing_raised`. I also wrote a test to ensure that my `create_all` method actually creates `Country` instances:

``` ruby
test "should create countries" do
    Country.delete_all
    Country.create_all
    assert_not_equal Country.all.count, 0, "Didn't create any countries"
  end
```
Here I used `assert_not_equal` to ensure that the number of `Country`s created was greater than 0. 

###User Model
My `User` model already included a validation to ensure that a `User` cannot create a profile with the same `username` as someone else: `validates :username, uniqueness: { case_sensitive: false }`

To test this, I created one `User` and then used an `assert_not` to ensure that a second `User` with the same username could not be saved:

```ruby
  test "should not allow duplicate user names" do
    user1 = User.create(username: "barryobama", password: "bo")
    user2 = User.new(username: "barryobama", password: "michelle")
    user1.save
    assert_not user2.save, "Saved a duplicate user name"
  end
```
If `user2` was able to be saved, the test would fail and I would see the message "Saved a duplicate user name". 

###User Starred Project Model
The final model in EffectiveDonate that I needed to test was `User_Starred_Project`. A user should not be able to star the same project multiple times, so I had already added the following validation to the model: `validates :user_id, uniqueness: {scope: :project_id}`.

In order to test this validation, I created a new `User` and a valid `Project` with all the required attributes. I then successfully created one instance of `UserStarredProject` using the `id` of both the `User` and `Project` that I created. I then attempted to save a duplicate `UserStarredProject`, using `assert_not` to ensure that it would not save: 

```ruby
  test "should prevent a user from starring multiple projects" do
    user1 = User.create(username: "barryobama10", password: "bo")
    project1 = Project.new
    project1.title = "Nourish a young brain, protect one ancient culture"
    project1.theme_str_id = "env"
    project1.image_url = "www.google.com/img"
    project1.project_link = "https://www.globalgiving.org/projects/nourish-a-young-brain-protect-one-ancient-culture/"
    project1.save
    user_star_1 = UserStarredProject.create(project_id: project1.id, user_id: user1.id)
    user_star_2 = UserStarredProject.new(project_id: project1.id, user_id: user1.id)
    assert_not user_star_2.save, "User starred a duplicate project"
  end
```
If there was a problem in my validation, I would see the message "User starred a duplicate project", and the test would fail. 


##Conclusion
Ultimately, this process of learning about unit testing and testing my own project's models was very helpful. For some reason, testing seemed like an intimidating skill to learn, but I realized it is actually not too difficult once you get started. I also am starting to understand its importance to maintaining well-organized and clean code. I probably wouldn't have added all of those validations to my `Project` model unless I started to test it, so my app is already more resilient to bad data than it was before. Also, the tests I wrote to check the API calls will be really helpful to ensure that my API credentials are still valid and that the endpoints I am reaching are correct.

I am looking forward to exploring more facets of testing in the near future, so stay tuned for more testing-related posts. Until next time!