Add bugs and see tests fail

In this part, we will intentionally add bugs to our app, so we can see what some different kinds of test failures look like.

Add a syntax error and detect it with a test

Edit the app/controllers/limericks_controller.rb file to add a syntax error to the LimericksController#index method as follows.

class LimericksController < ApplicationController

  before_action :authenticate_user!, except: [:index]
  before_action :require_permission, except: [:index, :new, :create]

  def index
    @limericks = Limerick.all.reverse_order
    renxxxxxxder :index
  end

  ...

Note that the call to render is now an invalid call to a undefined method renxxxxxxder. Although this example seems contrived, it is not uncommon for a developer to bump the keyboard while working in a file and to not notice the bug they introduced.

Run all tests to check for errors.

rails test -v

The output of this command should look like this:

Running 1 tests in a single process (parallelization threshold is 50)
Run options: -v --seed 61993

# Running:

LimericksControllerTest#test_should_get_index = 0.15 s = E


Error:
LimericksControllerTest#test_should_get_index:
NoMethodError: undefined method `renxxxxxxder' for #<LimericksController:0x0000000000d1d8>

    renxxxxxxder :index
    ^^^^^^^^^^^^
    app/controllers/limericks_controller.rb:8:in `index'
    test/controllers/limericks_controller_test.rb:6:in `block in <class:LimericksControllerTest>'


rails test test/controllers/limericks_controller_test.rb:4


Finished in 0.153665s, 6.5077 runs/s, 0.0000 assertions/s.
1 runs, 0 assertions, 0 failures, 1 errors, 0 skips

Note that error messages now appear in the test output. We can see that the undefined method is mentioned (“NoMethodError: undefined method \renxxxxxxder'") and that the file and line number where the bug was made is also mentioned ("app/controllers/limericks_controller.rb:8:in `index’"). Also, note that this bug is classified as an "error", because it essentially made the system crash (unlike a "failure", which is indicative of a failed assert` call).

Before moving on, correct the above bug and retest the code to ensure that the bug was fixed correctly.

Add a logic error and detect it with a test

Edit the app/controllers/limericks_controller.rb file to add a logic bug such that, in the index method, the call to Limerick.all.reverse_order is replaced with a call to Limerick.take(1) (which returns a collection of Limerick objects containing only 1 limerick).

class LimericksController < ApplicationController

  before_action :authenticate_user!, except: [:index]
  before_action :require_permission, except: [:index, :new, :create]

  def index
    @limericks = Limerick.take(1)
    render :index
  end

  ...

Note that we have 2 Limerick test fixtures, but our app will now retrieve only 1 of them (due to the take(1) call). In our LimericksControllerTest#should_get_index test case, the assertion assert_select 'div.card', limericks_size should now fail because the number of div.card elements printed will be 1, whereas the limericks_size value will be 2.

Run all tests to check for errors.

rails test -v

The output of this command should look like this:

Running 1 tests in a single process (parallelization threshold is 50)
Run options: -v --seed 31239

# Running:

LimericksControllerTest#test_should_get_index = 2.24 s = F


Failure:
LimericksControllerTest#test_should_get_index [/Users/sdf/workspace/comp7012/demo/limeriq-f23/test/controllers/limericks_controller_test.rb:8]:
Expected exactly 2 elements matching "div.card", found 1..
Expected: 2
  Actual: 1


rails test test/controllers/limericks_controller_test.rb:4


Finished in 2.243783s, 0.4457 runs/s, 0.8914 assertions/s.
1 runs, 2 assertions, 1 failures, 0 errors, 0 skips

Note that the assert_select 'div.card', limericks_size did indeed fail. The test output reports a failure in the LimericksControllerTest#test_should_get_index test case on line 8 of the limericks_controller_test.rb file (where the assert_select call is found). The output even mentions the cause of the assertion’s failure: “Expected exactly 2 elements matching "div.card", found 1..”. Note that this bug counts as a “failure” because it was caught by an assertion (and did not cause the system to crash as in the case of an “error”).

Before moving on, correct the above bug and retest the code to ensure that the bug was fixed correctly.

Add a syntax error that our tests miss

We will now add some errors that our tests fail to detect. The purpose of this exercise is to illustrate some of the risks of testing and to motivate the need to be thorough in writing tests.

Edit the app/controllers/user_limericks_controller.rb file to add a bug into the index method as follows.

class UserLimericksController < ApplicationController

  before_action :authenticate_user!

  def index
    @user = User.find(params[:user_id])
    @limericks = @user.limericks.reverse_order
    renxxxxxxder :index
  end

end

Note that, similar to the earlier example, the call to render is now an invalid call to a undefined method renxxxxxxder.

Run all tests to check for errors.

rails test -v

The output of this command should look like this:

Running 1 tests in a single process (parallelization threshold is 50)
Run options: -v --seed 389

# Running:

LimericksControllerTest#test_should_get_index = 2.11 s = .

Finished in 2.112802s, 0.4733 runs/s, 0.9466 assertions/s.
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips

Note that the test did not detect the obvious syntax error! How can this be? If you accidentally add a syntax error into a Ruby script, the error will not be revealed unless the script file with the error is loaded. Rails uses “lazy loading”, which means that script files are loaded only when they are used. In our case above, the file with the syntax error was never used by our test case, and therefore, the file was never loaded. Thus, thorough automated testing (which ensures that all code files are loaded and tested) is particularly important for discovering these sorts of errors.

Before moving on, correct the above bug.

Add a logic error that our tests miss

Edit the app/controllers/limericks_controller.rb file to add a logic bug such that, in the index method, the call to Limerick.all.reverse_order is replaced with a call to Limerick.take(2) (which returns a collection of Limerick objects containing only 2 limerick).

class LimericksController < ApplicationController

  before_action :authenticate_user!, except: [:index]
  before_action :require_permission, except: [:index, :new, :create]

  def index
    @limericks = Limerick.take(2)
    render :index
  end

  ...

Note that this code now clearly contains a logic error—no matter how many limericks are in the database, the app will only ever display 2 of them. However, because we happen to have 2 Limerick test fixtures, the assertion assert_select 'div.card', limericks_size will not report a failure. Thus, the logic error will be missed.

Run all tests to check for errors.

rails test -v

The output of this command should look like this:

Running 1 tests in a single process (parallelization threshold is 50)
Run options: -v --seed 389

# Running:

LimericksControllerTest#test_should_get_index = 2.11 s = .

Finished in 2.112802s, 0.4733 runs/s, 0.9466 assertions/s.
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips

Note that the test indeed misses this logic bug. The purpose of this example is to illustrate, yet again, the importance of writing tests that thoroughly test the code to minimize the number of bugs they might reasonably miss.

Before moving on, correct the above bug.


⏴ Back Next ⏵