Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users

Dashboard
Notifications
Mark all as read
Q&A

How do I write a new test case? Wiki

We have some limited automated testing already, particularly around core features. This guide covers how to create new tests.

Who can write tests?

Anyone who's comfortable with a little bit of relatively simple code.

What do I need?

You need at least:

  • the repository cloned to your local machine
  • a way of editing the code (you can use Notepad or similar, but an IDE like VS Code or Atom works much better)

Ideally, you'd also have:

  • a full development environment (i.e. you're able to run the application locally; see this guide on how to set that up)
  • comfortable running commands in your OS' terminal

You don't need significant development experience.

What can we test?

Our testing system simulates sending a request to the application, and then using the response to assert that various conditions are met, depending on what you want to test. This means that you can test:

  • whether the request succeeds (HTTP 200) or errors (HTTP 5xx class errors)
  • whether the correct changes are applied in the database
  • whether the format (HTML/JSON/etc) and content (messages, statuses, errors, etc) of the response is correct

You can't test:

  • the UI, or whether it sends the request in the correct format
  • most outside integrations, like SE OAuth or 3rd-party login (which can be tested, but often require more complex procedures or additional support to be set up — poke Art or luap42 instead)

How do I write a test?

Tests are held in the test/ directory of the repository. Most of our tests are controller tests, which live in test/controllers/ and do as described above - simulate a request, test the response.

The following set of steps is an exhaustive guide. If you vaguely know what you're doing, or like to learn it yourself by playing around with it, here's an example test that you can take and mess with:

test 'post edit should save and create history' do
  sign_in users(:standard_user)
  post = posts(:question_one)
  before_history = PostHistory.where(post_id: post.id).count
  post :update, params: { id: post.id, post: { title: 'test new post title' } }
  assert_response 200
  assert_not_nil assigns(:post)
  assert_equal 'test new post title', assigns(:post).title
  after_history = PostHistory.where(post_id: post.id).count
  assert_equal before_history + 1, after_history
end
  1. Identify the controller and action
    Once you know what you want to test, identify the controller and action that it lives under. Most of the time you can do this from the URL — a pathname of /posts/new (for example) is likely to be the new action of the posts controller — or, as Rails expresses it, posts#new. If you're not sure, you can try to find the path in config/routes.rb, or run rails routes in a console to dump a list of all paths, and find the path in there. If you can't find it, poke a developer.

  2. Write a skeleton test
    Controller tests follow this format:

    test 'give your test a descriptive name' do
      # sign-in & prepare
      # request
      # collection
      # test response
    end
    

    Not all tests need all of those stages. Add a skeleton test as above into the relevant file (i.e. if you're testing the comments controller, your test goes in test/controllers/comments_controller_test.rb), and give it a name that describes what the test is expecting.

  3. Prepare & request
    The first part of the test is any preparation, including sign-in or collecting any values that will be changed by the request that you want to test for. Let's say we're testing that when a user edits a post, the changes are saved and a PostHistory event is created. This needs:

    • a user to be signed in
    • a count of the history events before the edit, so we can check the count after is equal to one more than this

    You can sign a user in by using the sign_in helper. You must pass to it the user that you want to be signed in - there's a list of these in test/fixtures/users.yml (see "How do I use fixtures?", below, for more information). To sign in a standard user, add this line to your test:

    sign_in users(:standard_user)
    

    Pick the post you're going to test. Let's use :question_one, because :standard_user is the author for it so doesn't need permissions to edit it. Again, there's a list of posts in test/fixtures/posts.yml, and you can get the post instance with posts(:question_one). Add this line to your test:

    post = posts(:question_one)
    

    To get the history count, you need to select from the post_history table any events where the post ID matches the one we're testing, and count them. Add this line to your test:

    before_history = PostHistory.where(post_id: post.id).count
    
  4. Run the request
    Now you need to actually simulate the request. There are methods for each HTTP method — so if you need a GET request, you'll run get, and if you need a POST request it'll be post. You must pass to it the name of the action (which you identified earlier) that you're testing, and any parameters that you want to submit in the request. In this case, we'll just edit the title. The update action (which handles edits) expects a POST request with a post id, and a post object containing any updated details. So, our request will look like this — add this to your test:

    post :update, params: { id: post.id, post: { title: 'test new edited title' } }
    
  5. Collect results
    Before you make any assertions, collect any data or results you need to use. In this case, we need to know the number of post history events for this post after we've made the request, which should be one more than before it. Same code as before for this, but in a different variable:

    after_history = PostHistory.where(id: post.id).count
    
  6. Test!
    To test, you use assert methods. The most simple is assert, which passes if the data or condition you pass to it is true, and fails otherwise. There are plenty of others, but we'll use the most common here. You should use the most specific assert method for what you're trying to test, as that makes the error message more specific when it fails.

    For this test, we'll test that:

    • the response is a success (HTTP 200)
    • the server assigns a @post variable for the post instance
    • the @post has been updated with our new title
    • there is 1 more history event than there was before the request

    Add these lines to your test:

    assert_response 200
    assert_not_nil assigns(:post)
    assert_equal 'test new edited title', assigns(:post).title
    assert_equal before_history + 1, after_history
    
  7. Run your test
    You can run the full test suite by running rails test (or rails t, for shorthand) in a terminal. Running the full suite will take a while, though (10 minutes or more), so you can also pass in the path to the file containing your test, and Rails will run just the tests in that file. In this case, run:

    rails test test/controllers/posts_controller_test.rb
    
  8. PR
    Now submit a pull request on GitHub to add your changes - either push the changes to your own fork if you don't have push access and pull request from there, or push to a branch and open the PR from there. If you can get all the tests to pass including your new tests, great! If you're having trouble, you can always submit the PR anyway and developers will be able to help you get the test right.


The above is all you need to successfully write a test case. If you want more detailed information, read on.

How do I use fixtures?

Fixtures are pre-set-up data for each table we have in the database, so that you have some data to test on. The steps above used a couple of fixtures in users(:standard_user) and posts(:question_one).

Fixtures are held under test/fixtures/. Each data type has its own file, and each file contains multiple fixtures for that type. For instance, for users, we've already defined standard users, editors (edit_posts ability), deleters (flag_curate ability), moderators, admins, and global mods and admins. For posts, we have a mix of questions, answers, and articles, in various states.

If you're looking to test on a particular condition, have a look at the fixtures to see if what you need is there. For instance, you might be looking to test that a standard user can't post in a high-trust-level category - for that, you'd need the standard user and the high-trust-level category defined in fixtures.

Fixture files are written in YAML. Each fixture consists of an identifying name (like standard_user), then the attributes for that fixture, which correspond to the database fields. For instance, this is an example definition for a user:

test_user:
  username: standard-user
  email: standard@qpixel-test.net
  profile_markdown: ~

You'd access that user in test cases with users(:test_user). Same for any other fixture: categories(:test_category) or audit_logs(:random_name_here) also work. If the random_name_here AuditLog isn't defined, it'll just return nil for that fixture, so make sure the fixture you're referencing is actually defined.

More help/documentation

Why does this post require moderator attention?
You might want to add some details to your flag.

0 comments

This community is part of the Codidact network. We have other communities too — take a look!

You can also join us in chat!

Want to advertise this community? Use our templates!