「あれ、rspecで書いた通るはずのテストが通らない・・?」
業務でrspecを書いた時に遭遇したこの事象。原因はlet / let!に関する理解の甘さでした。n番煎じではありますが、備忘としてまとめておきたいと思います。
let
letはrspecリポジトリにおいて、ClassMethodsモジュール下に実装されています。
module ClassMethods
...
def let(name, &block)
# We have to pass the block directly to `define_method` to
# allow it to use method constructs like `super` and `return`.
raise "#let or #subject called without a block" if block.nil?
# A list of reserved words that can't be used as a name for a memoized helper
# Matches for both symbols and passed strings
if [:initialize, :to_s].include?(name.to_sym)
raise ArgumentError, "#let or #subject called with reserved name `#{name}`"
end
our_module = MemoizedHelpers.module_for(self)
# If we have a module clash in our helper module
# then we need to remove it to prevent a warning.
#
# Note we do not check ancestor modules (see: `instance_methods(false)`)
# as we can override them.
if our_module.instance_methods(false).include?(name)
our_module.__send__(:remove_method, name)
end
our_module.__send__(:define_method, name, &block)
# If we have a module clash in the example module
# then we need to remove it to prevent a warning.
#
# Note we do not check ancestor modules (see: `instance_methods(false)`)
# as we can override them.
if instance_methods(false).include?(name)
remove_method(name)
end
# Apply the memoization. The method has been defined in an ancestor
# module so we can use `super` here to get the value.
if block.arity == 1
define_method(name) { __memoized.fetch_or_store(name) { super(RSpec.current_example, &nil) } }
else
define_method(name) { __memoized.fetch_or_store(name) { super(&nil) } }
end
end
詳しい処理の解説はこちらがわかりやすいですが、実装内容をざっくりまとめると「引数で渡されたnameを、いつでも呼び出された時に評価できるようにプログラムとして覚えておく」というイメージです。遅延評価と呼ばれる所以ですね。
let!
let!もletと同じくClassMethodsモジュールに実装されており、「letで名前を登録してbeforeですぐ評価!」という一種のラッパーメソッドになっています。先にも書いたように、letで宣言した値はそれが本当に必要とされる時まで評価が遅延されますが、let!はそのメソッド内に「本当に必要とされる時」を作ることで値の評価までまるっと行っているのですね。
def let!(name, &block)
let(name, &block)
before { __send__(name) }
end
つまり、テストの実行にあたって「ユーザー情報は前提条件として定義されていて欲しい!」というような場合は、let!で書いておく必要があります。私はそれを知らず、letを使って書いてハマりました^^;
返信がありません