やりたいこと
某gem(この記事では例としてfb_graphを使用)を呼んだときにMockした別のモジュール・クラスを呼びたい。
もう少し言うと、
- Mockをonにして、FbGraphを呼ぶとMock::FbGraphが呼ばれる
- Mockをoffにして、FbGraphを呼ぶとFbGraphが呼ばれる
gemのrequireとObjectクラスへの追加
fb_graphをrequireするとObjectクラスにFbGraphが追加されます。
FbGraph = Mock::FbGraphでFbGraphを置き換えれますが、単純にこれをするとwarningが出てしまいます。
Object.constants.include?(:FbGraph) #=> false
require 'fb_graph'
Object.constants.include?(:FbGraph) #=> true
FbGraph #=> FbGraph
module Mock; module FbGraph; end; end
FbGraph = Mock::FbGraph; #=> warning: already initialized constant FbGraph
Object.constants.include?(:FbGraph) #=> true
FbGraph #=> Mock::FbGraph
requireで読み込んだgemをundefする
上記warning対策。
さらに、定数(FbGraph)の設定はconst_setを使うようにする。
require 'fb_graph'
Object.constants.include?(:FbGraph) #=> true
FbGraph #=> FbGraph
Object.send(:remove_const, :FbGraph)
Object.constants.include?(:FbGraph) #=> false
module Mock; module FbGraph; end; end
Object.const_set(:FbGraph, Mock::FbGraph)
Object.constants.include?(:FbGraph) #=> true
FbGraph #=> Mock::FbGraph
Mockのon/offを作ってみる
以上を踏まえてMockにon/off機能を作ってみる
require 'fb_graph'
module Mock
extend self
def on
if ::FbGraph != Mock::FbGraph
Object.const_set(:SourceFbGraph, ::FbGraph)
Object.send(:remove_const, :FbGraph) if Object.constants.include?(:FbGraph)
Object.const_set(:FbGraph, Mock::FbGraph)
end
end
def off
if ::FbGraph == Mock::FbGraph
Object.send(:remove_const, :FbGraph) if Object.constants.include?(:FbGraph)
Object.const_set(:FbGraph, ::SourceFbGraph)
Object.send(:remove_const, :SourceFbGraph) if Object.constants.include?(:FbGraph)
end
end
module FbGraph
end
end
p FbGraph #=> FbGraph
Mock.on
p FbGraph #=> Mock::FbGraph
Mock.off
p FbGraph #=> FbGraph