Jonathan Musso
Jonathan Musso

Reputation: 1374

First RSPEC test, ensure value is 1 or -1

New programmer here. I am a student working on my project that is a Reddit clone. Currently I have been introduced to RSPEC. I have to start writing my own Model tests to be used in further exercises. The model in question is not created, it will be in the next assignment. Can someone please check if I have done this correctly?

In the next checkpoint, we'll add a Vote model. This model will feature an inclusion validation. Inclusion validation ensures that a vote's value attribute is either 1 or -1. If a vote is initialized with any other value, it will not save.

  1. Create VoteSpec:

spec/models/vote_spec.rb

describe Vote do
  describe "validations" do
    describe "value validation" do
      it "only allows -1 or 1 as values" do
        # your expectations here
      end
    end
  end
end

Write a spec that asserts the validations work as expected.

Use RSpec's expect().to eq() syntax. As you may recall from the specs in the Ruby exercises, you can assert that something should equal false or true.

You won't be able to run the tests because we haven't generated the model we're testing.

Below is my implementation:

describe Vote do
  describe "validations" do

    before do     
      2.times { @vote.create(value: 1) }
      3.times { @vote.create(value: -1) }
      2.times { @vote.create(value: 3) }
    end

    describe "value validation" do 
      it "only allows -1 or 1 as values" do
        expect ( @vote.value ).to eq(-1)
      end

      it "only allows -1 or 1 as values" do
        expect ( @vote.value ).to eq(1)
      end
    end
  end
end

Best regards.

Edit: Here is a revision:

describe Vote do
  describe "validations" do

    before do     
      2.times { Vote.create(value: 1) }
      3.times { Vote.create(value: 0) }
      2.times { Vote.create(value: 3) }
    end

    describe "value validation" do 
      it "only allows -1 as value" do
        expect ( @vote.value ).to eq(-1)
      end
      it "only allows 1 as value" do
        expect ( @vote.value ).to eq(1)
      end

      it "it prohibits other values" do
        expect( @vote.value ).to_not be_valid
      end
    end
  end
end

I have also tried with this code, which worked at first but now fails in the next assignment:

require 'rails_helper'

  describe Vote do
   describe "value validation" do

      it "allows -1" do
        value = Vote.create(value: -1)
        expect(value).to be_valid
      end

      it "allows +1" do
        value = Vote.create(value: +1)
        expect(value).to be_valid
      end

      it "prohibits other values" do
        value = Vote.create(value: 0)
        expect(value).to_not be_valid
      end

    end
end

▶ rspec spec
...FFF

Failures:

  1) Vote value validation allows -1
     Failure/Error: value = Vote.create(value: -1)
     NoMethodError:
       undefined method `update_rank' for nil:NilClass
     # ./app/models/vote.rb:12:in `update_post'
     # ./spec/models/vote_spec.rb:7:in `block (3 levels) in <top (required)>'

  2) Vote value validation allows +1
     Failure/Error: value = Vote.create(value: +1)
     NoMethodError:
       undefined method `update_rank' for nil:NilClass
     # ./app/models/vote.rb:12:in `update_post'
     # ./spec/models/vote_spec.rb:12:in `block (3 levels) in <top (required)>'

  3) Vote value validation prohibits other values
     Failure/Error: expect(value).to eq(false)

       expected: false
            got: #<Vote id: nil, value: 0, user_id: nil, post_id: nil, created_at: nil, updated_at: nil>

       (compared using ==)
     # ./spec/models/vote_spec.rb:18:in `block (3 levels) in <top (required)>'

Finished in 0.30485 seconds (files took 3.28 seconds to load)
6 examples, 3 failures

Failed examples:

rspec ./spec/models/vote_spec.rb:6 # Vote value validation allows -1
rspec ./spec/models/vote_spec.rb:11 # Vote value validation allows +1
rspec ./spec/models/vote_spec.rb:16 # Vote value validation prohibits other values

Upvotes: 6

Views: 2602

Answers (4)

SkyWriter
SkyWriter

Reputation: 1479

It seems to me what to test is already in the task itself:

This model will feature an inclusion validation. Inclusion validation ensures that a vote's value attribute is either 1 or -1. If a vote is initialized with any other value, it will not save.

Let's walk step by step:

describe Vote do
  describe 'validations' do
    it 'should treat -1 vote value as valid' do
      # 1. Prepare the environment. That comes right from the task: we need
      #    to create a model with a vote value of -1.
      # 2. Do something. Again, from the task: let's try saving it.
      # 3. Verify the result. From the tasks again: it should save.
    end
  end
end

Now that we know what to do, let's go and write some code:

describe Vote do
  describe 'validations' do
    it 'should treat -1 vote value as valid' do
      # 1. Prepare the environment. That comes right from the task: we need
      #    to create a model with a vote value of -1.
      vote = Vote.new(value: -1)

      # 2. Do something. Again, from the task: let's try saving it.
      result = vote.save
      # (according to ActiveRecord specifications `.save` returns
      # `true` when it succeeds saving, and `false` otherwise.

      # 3. Verify the result. From the tasks again: it should save.
      expect(result).to be_true
    end

    it 'should treat 1 vote value as valid' do
      # Prepare the environment
      vote = Vote.new(value: 1)
      # Do something
      result = vote.save
      # Verify the result
      expect(result).to be_true
    end

    it 'should treat 0 vote value as invalid' do
      # Prepare the environment
      vote = Vote.new(value: 0)
      # Do something
      result = vote.save
      # 3. Verify the result. From the task: it should *not* save.
      expect(result).to be_false
    end
  end
end

Now we have the code that suits your needs: it validates the model. There are a couple of issues with it:

  • It will fail in case there are any other validations, besides the value field
  • It's not the way specs are written
  • It's a little bit verbose

Let's address those (I'll strip the comments):

describe Vote do
  describe 'validations' do
    let(:overrides) { { } }
    let(:params) { { value: 1 }.merge(overrides) } # valid by default
    subject { Vote.new(params).save }

    context 'vote == -1 is valid' do
      let(:overrides) { { value: -1 } } # override one parameter
      it { is_expected.to be_true }
    end

    context 'vote == 1 is valid' do
      let(:overrides) { { value: 0 } }
      it { is_expected.to be_true }
    end

    context 'vote == 0 is invalid' do
      let(:overrides) { { value: 0 } }
      it { is_expected.to be_false }
    end
  end
end

You can make this even cleaner and more readable in at least two ways, should you need it:

  1. Roll out your own RSpec helpers.
  2. Use 3rd party libraries, which essentially contain those helpers already written for you. They will change your validation verification code to something like:

    describe Vote do
      it { is_expected.to validate_inclusion_of(:value).in_array([-1, 1]) }
    end
    

    Neat, huh? :) But I don't think that that's what is expected from you in this exercise.

Hope this helps!

Upvotes: 0

JSFernandes
JSFernandes

Reputation: 188

Jonathan, according to your instructions, you should be trying to save/validate the vote and see whether the result is true or false. You should also use let to make your code cleaner and build to make sure that the vote isn't saved until you explicitly do so. Here is how I would handle this situation:

describe Vote do
  describe "validations" do
    let(:vote) { Vote.new(value: vote_value) }

    context "when the value is 1" do
      let(:vote_value) { 1 }

      it "successfully saves" do
        expect(vote.save).to eq(true)
      end
    end

    context "when the value is -1" do
      let(:vote_value) { -1 }

      it "successfully saves" do
        expect(vote.save).to eq(true)
      end
    end

    context "when the value is 0" do
      let(:vote_value) { 0 }

      it "does not save" do
        expect(vote.save).to eq(false)
      end
    end
  end
end

Feel free to replace those vote.save by vote.valid?

Upvotes: 1

cbliard
cbliard

Reputation: 7262

You can use RSpec compound expectations with or:

it "only allows -1 or 1 as values" do
  expect ( @vote.value ).to eq(1).or eq(-1)
end

Upvotes: 2

errata
errata

Reputation: 26952

In this slightly particular case you could just use the absolute value.

 it "only allows -1 or 1 as values" do
    expect ( @vote.value.abs ).to eq(1)
 end

Upvotes: 3

Related Questions