rspec 测试中变量既为 nil 也非 nil
Variable is both nil as well as not nil within rspec test
这是我多年编程中见过的最奇怪的错误。请忽略我在下面使用的奇怪习语,因为我正在使用 trailblazer,一个用于 Rails:
的编程框架
下面的代码
let (:app) { App::Create.(app: {name: "Half-Life", steam_id: "70"}).model }
it 'gets app details from Steam' do
VCR.use_cassette('app/get') do
p app.id
app_id = app.id
app = App::Get.(id: app_id).model
end
expect(app.app_type).to eq('game')
end
按预期工作。
下面的代码
let (:app) { App::Create.(app: {name: "Half-Life", steam_id: "70"}).model }
it 'gets app details from Steam' do
VCR.use_cassette('app/get') do
p app.id
app = App::Get.(id: app.id).model
end
expect(app.app_type).to eq('game')
end
在
行中为 nil:NilClass 抛出一个未定义的方法“id”
app = App::Get.(id: app.id).model
但是行
p app.id
就在违规行显示正确 ID 之前。
可能发生了什么?
这一行:
let (:app) { App::Create.(app: {name: "Half-Life", steam_id: "70"}).model }
创建一个名为 app
的方法,调用时 return 是一个 App
。 (它会记住结果,因此在初始调用后它总是 return 相同 App
。)
这一行:
app = App::Get.(id: app.id).model
创建一个名为 app
的 新局部变量 ,它与方法 app
不明确。根据the Ruby docs on assignment:
In Ruby local variable names and method names are nearly identical. If
you have not assigned to one of these ambiguous names ruby will assume
you wish to call a method. Once you have assigned to the name ruby
will assume you wish to reference a local variable.
The local variable is created when the parser encounters the
assignment, not when the assignment occurs:
因此,当您尝试分配给 app
时,会创建一个新的局部变量。当评估赋值的右侧时,app.id
尝试在局部变量 app
(目前为 nil
)上调用 id
。
这是我能想到的最简单的重现问题的代码:
def a
"Hello"
end
p a.upcase # "HELLO"
a = a.upcase # undefined method `upcase' for nil:NilClass (NoMethodError)
有几种方法可以修复您的代码。我认为目前最好的解决方案是:
app2 = App::Get.(id: app.id).model
方法已使用名称 app
...不要通过创建具有相同名称的局部变量来隐藏它。
你也可以这样做:
app = App::Get.(id: app().id).model # the parens disambiguate
或您已有的解决方案:
app_id = app.id # capture the ID before the local variable gets created
app = App::Get.(id: app_id).model
但我认为这两者都比不首先创建命名冲突更糟糕。
这是我多年编程中见过的最奇怪的错误。请忽略我在下面使用的奇怪习语,因为我正在使用 trailblazer,一个用于 Rails:
的编程框架下面的代码
let (:app) { App::Create.(app: {name: "Half-Life", steam_id: "70"}).model }
it 'gets app details from Steam' do
VCR.use_cassette('app/get') do
p app.id
app_id = app.id
app = App::Get.(id: app_id).model
end
expect(app.app_type).to eq('game')
end
按预期工作。
下面的代码
let (:app) { App::Create.(app: {name: "Half-Life", steam_id: "70"}).model }
it 'gets app details from Steam' do
VCR.use_cassette('app/get') do
p app.id
app = App::Get.(id: app.id).model
end
expect(app.app_type).to eq('game')
end
在
行中为 nil:NilClass 抛出一个未定义的方法“id”app = App::Get.(id: app.id).model
但是行
p app.id
就在违规行显示正确 ID 之前。
可能发生了什么?
这一行:
let (:app) { App::Create.(app: {name: "Half-Life", steam_id: "70"}).model }
创建一个名为 app
的方法,调用时 return 是一个 App
。 (它会记住结果,因此在初始调用后它总是 return 相同 App
。)
这一行:
app = App::Get.(id: app.id).model
创建一个名为 app
的 新局部变量 ,它与方法 app
不明确。根据the Ruby docs on assignment:
In Ruby local variable names and method names are nearly identical. If you have not assigned to one of these ambiguous names ruby will assume you wish to call a method. Once you have assigned to the name ruby will assume you wish to reference a local variable.
The local variable is created when the parser encounters the assignment, not when the assignment occurs:
因此,当您尝试分配给 app
时,会创建一个新的局部变量。当评估赋值的右侧时,app.id
尝试在局部变量 app
(目前为 nil
)上调用 id
。
这是我能想到的最简单的重现问题的代码:
def a
"Hello"
end
p a.upcase # "HELLO"
a = a.upcase # undefined method `upcase' for nil:NilClass (NoMethodError)
有几种方法可以修复您的代码。我认为目前最好的解决方案是:
app2 = App::Get.(id: app.id).model
方法已使用名称 app
...不要通过创建具有相同名称的局部变量来隐藏它。
你也可以这样做:
app = App::Get.(id: app().id).model # the parens disambiguate
或您已有的解决方案:
app_id = app.id # capture the ID before the local variable gets created
app = App::Get.(id: app_id).model
但我认为这两者都比不首先创建命名冲突更糟糕。