RSpec Expectations and ExpectationNotMetError - ruby

Hello I have been following a course to further my understanding of cucumber. The course is a little old so i have had to update some rspec syntax from should to expect but otherwise have been following it very carefully. I cannot get this recent test to pass and i am rather lost as to why. The error i am receiving is below. I understand it is receiving nil when it should be receiving 95 but i still do not understand why.
When They submitted an assignment # features/step_definitions/teacher_grade_assignment.rb:6
Then the assignment has a grade # features/step_definitions/teacher_grade_assignment.rb:14
expected: 95
got: nil
(compared using ==)
(RSpec::Expectations::ExpectationNotMetError)
./features/step_definitions/teacher_grade_assignment.rb:15:in `/^the assignment has a grade$/'
features/teacher_can_grade_assignment.feature:11:in `Then the assignment has a grade'
Failing Scenarios:
cucumber features/teacher_can_grade_assignment.feature:7 # Scenario: Teacher can grade assignment
teacher_grade_assignment.rb
Given(/^I have a student$/) do
#teacher = Teacher.new
#student = Student.new
end
Given(/^They submitted an assignment$/) do
#teacher.submit_assignment(#student, Assignment.new)
end
When(/^I grade the assignment$/) do
#teacher.record_grade(#student, 95)
end
Then(/^the assignment has a grade$/) do
expect(#teacher.assignment_for_student(#student).grade).to eq(95)
end
student_assignment_steps.rb
Given(/^I am a student$/) do
#student = Student.new #setting as an instance variable to access later on
#teacher = Teacher.new
end
When(/^I submit an assignment to my teacher$/) do
#assignment = Assignment.new
#teacher.submit_assignment(#student, #assignment)
end
Then(/^my teacher should have my assignment$/) do
expect(#teacher.assignment_for_student(#student)).to eq(#assignment)
end
teacher.rb
class Teacher
def initialize
#assignments = {}
end
def record_grade(student, grade)
assignment = #assignments[student] #assignment equal to assignments of the student
assignment.grade = grade
#assignments[student] = assignment
end
def submit_assignment(student, assignment)
#assignments[student] = assignment
end
def assignment_for_student(student)
#assignments[student]
end
end
teacher_spec.rb
require_relative "../lib/teacher"
require "rspec"
describe Teacher do
it "should store assignments" do
student = double
assignment = double
subject.submit_assignment(student, assignment)
expect(subject.assignment_for_student(student)).to eq(assignment)
end
describe "should record a grade" do
it "should record and the grade" do
student = double
assignment = double
expect(assignment).to receive(:grade=).with(95)
subject.submit_assignment(student, assignment)
subject.record_grade(student, 95)
end
end
end
assignment_spec.rb
require_relative "../lib/assignment"
describe Assignment do
it "should store a grade" do
subject.grade = 60
expect(subject.grade).to eq(60)
end
end
assignment.rb
class Assignment
attr_accessor :grade
end
student.rb
class Student
end
teacher_can_grade_assignment.feature
Feature: Teacher can grade assignment
As a Teacher
I can grade my students' assignments
So that they can know their knowledge level
Scenario: Teacher can grade assignment
Given I have a student
And They submitted an assignment
When They submitted an assignment
Then the assignment has a grade

Your assignment has no grade because the teacher never graded it: your feature doesn't call the "I grade the assignment step"

Your tests are telling you that #teacher.assignment_for_student(#student).grade is nil. Somehow it is not set up correctly. This is unexpected as you state, you expected it to be 95.
In TDD the best next step is to figure out more of the state in the failing test.
Add some extra (temporary) expectations to see what the state of your objects is in the failing step:
expect(#teacher.assignment_for_student(#student)).to eq(#assignment)
expect(#teacher.class_variable_get(:#assigments).to include(#assignment)
Even tests that you are sure will fail can often help a lot.
expect(#teacher.assignment_for_student(#student)).to be "failure"
By sprinkling such expectations around your steps, you can debug the state and see where the code is integrated wrong.
From your pasted code, I don't see anything wrong with the code immediately.

Related

RSpec: simulating user input (via gets) without the test prompting for it

I'm coding a game by taking a TDD first approach, and have gotten stuck because the test keeps stopping for user input (repo is here).
I want the test to simulate user input rather than prompting for it, as I've set up some let keywords and have tried to account for user input that comes in via gets.chomp.
Here is where the game prompts for user input:
game.rb
module ConnectFour
class Game
def start_game
puts 'Welcome to Connect Four.'
puts "Enter name of player 1 (red)"
player1name = gets.chomp
player1 = Player.new(player1name)
end
end
end
And here is the test code:
game_spec.rb
require 'spec_helper'
module ConnectFour
describe Game do
let(:game) { Game.new }
let(:player1name) { 'Bob' }
let(:player1) { Player.new(player1name) }
describe 'Instantiate game play objects' do
describe 'Create player 1' do
it 'Provide player 1 name' do
allow_any_instance_of(Kernel)
.to receive(:gets)
.and_return(player1name)
end
it 'Instantiate player 1' do
expect(player1.name).to eq player1name
end
end
end # describe 'Instantiate game play objects'
end # Describe 'Game'
end
So far I've tried encapsulating the gets.chomp in its own method as recommended here but this has no effect. I've also tried prefixing $stdin to gets.chomp statements in the Ruby code but yeah, that was pretty useless. I had asked a similar question here recently and thought I had understood how to simulate user input but obviously not... any help would be appreciated.
use allow_any_instance_of(Object) instead of Kernel. The module Kernel is included into Object. Kernel is not ever actually instantiated because it's a module.
kind of a small point, but it'd be more accurate if you stubbed gets to return a strinng ending in \n, otherwise you could remove the chomp from the tested functionn and the test will still pass
reproducable example
require 'rspec'
require 'rspec/expectations'
test_case = RSpec.describe "" do
it "" do
allow_any_instance_of(Object).to receive(:gets).and_return "something\n"
puts gets.chomp
end
end
test_case.run

Struggling to write code and tests for my Tube System program, TDD using Rspec and Ruby

I am writing a small program for a train system.
I have a passenger, coach, train and station class (and thus, a spec test for each).
My test for my passenger class is as such:
let (:passenger) {Passenger.new}
it "should not be touched in to a station when initialized" do
expect(passenger.touchedin?).to be false
end
it "should be able to enter coach" do
coach = Coach.new
passenger.enter(coach)
expect{coach.to receive(:enter)}
end
it "should be able to alight coach" do
coach = Coach.new
passenger.alight(coach)
expect{coach.to receive(:alight)}
end
it "should be able to touch into station" do
station = Station.new
passenger.touchin(station)
expect{station.to receive(:touchin)}
end
it "should be able to touch out of station" do
station = Station.new
passenger.touchout(station)
expect{station.to receive(:touchout)}
end
end
And my passenger class is like this (at the moment :p):
class Passenger
def initialize
#touchedin = false
end
def enter(coach)
end
def touchedin?
#touchedin
end
def alight(coach)
end
def touchin(station)
end
def touchout(station)
end
end
I am unsure how to satisfy my tests, if my tests are even correct in the first place.
Any help is really appreciated!
You've not really said how you're modeling the relationship between coaches and passengers, but one way I could think of could be as follows. I'm just putting enough for the coach/passenger relationship (so nothing about touching in as this involves the station) - and I'm using minitest syntax, but I think you can get the idea of what's happening.
class Coach
def initialize
#passengers = []
end
...
end
class Passenger
def initialize
#touched_in = false
end
def alight(coach)
coach.passengers << self.uid # or self, if you want the whole passenger object available
end
...
end
coach = Coach.new
assert_empty coach.passengers
joe = Passenger.new
refute_includes coach.passengers, joe.uid # or joe
joe.alight(coach)
assert_includes coach.passengers, joe.uid # or joe

rspec way for passing variable between multiple contexts

I was wondering what would be the best way to pass variable between multiple contexts (or multiple its) in rspec but without using global variables?
For example, I have this:
describe "My test" do
let(:myvar) { #myvar = 0 }
context "First test pass" do
it "passes" do
myvar = 20
expect(myvar).to eq(20)
end
end
context "Second test pass" do
it "passes" do
expect(myvar).to eq(20)
end
end
end
Now, obviously, this will not work with let because with new context, myvar variable will be back on initial state which is = 0.
I would need mechanism to "cache state" between two contexts which would in turn give me value of myvar = 20 in second context
Any opinions, suggestions and improvements are welcome.
Thanks
Another simple way, would be to define a 'local variable' in describe context.
the 'local variable' would live throughout the describe, and any changes during run time would effect it, and so change it.
For example
describe 'tests' do
context 'Sharing a variable across tests' do
var = 1
puts var
it "it one. var = #{var}" do
var = var*2
puts var
end
it "it two" do
puts var
end
end
end
Output
1
2
1
What happens is not what you think happens.
What you want to happen break "unit testing" as a methodology.
Let me explain #2 first - unit testing test cases should be able to work in isolation, which means that they should work when run together, when run apart, and in any order... so much so that some unit testing frameworks (like the default one in elixir) run test cases in parallel...
As for #1 - when you write myvar = 20 you are not assigning a value to let(:myvar) { #myvar = 0 }, you simply create a local variable, which will override all calls to myvar within the method, but will not be available outside the method (myvar will return 0).
Even if you would have set #myvar = 20 (unless you do it before you call myvar for the first time) instead, myvar would still return 0, since the let function is using a memento pattern, which means it is called once, and subsequent calls return the value originally returned (in this case 0):
puts myvar
#myvar = 20
puts myvar
# => 0
# => 0
I just ran into this same problem. How I solved it was by using factory_girl gem.
Here's the basics:
create a factory:
require 'factory_girl'
require 'faker' # you can use faker, if you want to use the factory to generate fake data
FactoryGirl.define do
factory :generate_data, class: MyModule::MyClass do
key 100 # doesn't matter what you put here, it's just a placeholder for now
another_key 'value pair'
end
end
Now after you made the factory you need to make a Model that looks like this:
Module MyModule
class MyClass
#for every key you create in your factory you must have a corresponding attribute accessor in the model.
attr_accessor :key, :another_key
#you can also place methods here to call from your spec test, if you wish
# def self.test
#some test
# end
end
end
Now going back to your example you can do something like this:
describe "My test" do
let(:myvar) { #myvar }
context "First test pass" do
it "passes" do
#myvar.key = 20 #when you do this you set it now from 100 to 20
expect(#myvar.key).to eq(20)
end
end
context "Second test pass" do
it "passes" do
expect(#myvar.key).to eq(20) #it should still be 20 unless you overwrite that variable
end
end
end
As stated by others, not proper way of unit testing. But, how should we know if you're unit testing or not. So, I won't judge.
Anyways, good luck let us know, if you got some other solution!

Assert_equal undefined local variable LRTHW ex52

Hi I made it to the lase exercise os Learn Ruby The Hard Way, and I come at the wall...
Here is the test code:
def test_gothon_map()
assert_equal(START.go('shoot!'), generic_death)
assert_equal(START.go('dodge!'), generic_death)
room = START.go("tell a joke")
assert_equal(room, laser_weapon_armory)
end
And here is the code of the file it should test:
class Room
attr_accessor :name, :description, :paths
def initialize(name, description)
#name = name
#description = description
#paths = {}
end
def ==(other)
self.name==other.name&&self.description==other.description&&self.paths==other.paths
end
def go(direction)
#paths[direction]
end
def add_paths(paths)
#paths.update(paths)
end
end
generic_death = Room.new("death", "You died.")
And when I try to launch the test file I get an error:
generic_death = Room.new("death", "You died.")
I tried to set the "generic_death = Room.new("death", "You died.")" in test_gothon_map method and it worked but the problem is that description of the next object is extremely long, so my questions are:
why assertion doesn't not respond to defined object?
can it be done different way then by putting whole object to testing method, since description of the next object is extremely long...
The nature of local variable is that they are, well, local. This means that they are not available outside the scope they were defined.
That's why ruby does not know what generic_death means in your test.
You can solve this in a couple of ways:
define rooms as constants in the Room class:
class Room
# ...
GENERIC_DEATH = Room.new("death", "You died.")
LASER_WEAPON_ARMORY = Room.new(...)
end
def test_gothon_map()
assert_equal(Room::START.go('shoot!'), Room::GENERIC_DEATH)
assert_equal(Room::START.go('dodge!'), Room::GENERIC_DEATH)
room = Room::START.go("tell a joke")
assert_equal(room, Room::LASER_WEAPON_ARMORY)
end
assert the room by its name, or some other identifier:
def test_gothon_map()
assert_equal(START.go('shoot!').name, "death")
assert_equal(START.go('dodge!').name, "death")
room = START.go("tell a joke")
assert_equal(room.name, "laser weapon armory")
end

How display failure on undescribed example in RSpec?

I am describing class on RSpec
class Pupil
def initialize(name, dateOfBirth)
#name = name
#dateOfBirth = dateOfBirth
end
def name
#name
end
def ages
#should calculate ages
end
end
describe Pupil do
context do
pupil = Pupil.new("Stanislav Majewski", "1 april 1999")
it "should returns name" do
pupil.name.should eq("Stanislav Majewski")
end
it "should calculates ages" do
#not described
end
end
end
RSpec returns:
..
Finished in 0.00203 seconds
2 examples, 0 failures
Is there an elegant way to display a failure message that the method is not described?
If you're concerned that you'll create a test and forget to put anything it in (sometimes I'll create three tests I know I'll need, and work on each of them in turn) then you can do the following:
it "should calculates ages" do
fail
end
OR
it "should calculates ages"
...and that's all (no block) will mark the test as pending automatically. In other words, don't fill out your tests until they have actual test code in them.
Also, if you don't test any assertions (i.e. if your spec doesn't contain any lines that have a call to should in them), your spec will appear to pass. This has happened to me a few times, where I write a new test, expecting it to fail, and it doesn't because I forgot to include the call to should which is what actually tests the assertion.

Resources