2012-12-19 43 views
6

鉴于一个典型的ActiveRecord模型,我经常有before_save回调解析输入,例如从用户采取类似time_string和解析它到time字段。Rails:如何测试before_save回调

即设置看起来像这样:

before_save :parse_time 
attr_writer :time_string 

private 
def parse_time 
    time = Chronic.parse(time_string) if time_string 
end 

据我所知,它被认为是最好的做法,使回调方法私有。但是,如果他们是私人的,那么你不能单独调用他们来单独测试他们。

因此,对于你来说,经验丰富的Rails测试人员,你如何处理测试这种事情?

+0

你在哪里使用'time'变量?是你的对象的属性? –

+0

上面的例子是一种捏造,但是,时间变量是一个对象属性。 – Andrew

回答

9

在Ruby中,私有方法仍然可以通过Object#send

你可以利用这个为你的单元测试,像这样:

project = Project.new 
project.time_string = '2012/11/19 at Noon' 
assert_equal(project.send(:parse_time), '2012-11-19 12:00:00') 
+0

有趣的是,我没有意识到'#send'的工作方式。谢谢! – Andrew

3

我会做的是保存newbuild实例的状态将对象保存并根据已更改属性的值进行断言或期望before_save

post = Post.new 
post.time_string = '2012/11/19' 
expected_time = Chronic.parse(post.time_string) 
post.save 
assert_equal(post.time, expected_time) 

这样你就可以测试对象应该如何操作的行为,而不一定是方法的实现。

+2

好的,但这有两个主要缺点:(1)速度慢。 (2)'#save'运行所有的回调函数,并且如果另外一个回调错误超出了测试的范围,那么也会失败,即使它可能正在工作。 – Andrew

+0

我也喜欢这种方式,因为只要结束状态是正确的,它的内部工作方式应该没有问题,但是直接测试方法有很多优点(速度,覆盖率和理解力)。 – Unixmonkey

+0

回复:测试行为与实现,我明白你的意思,但这里的目标是单独测试方法的行为,而不是覆盖多个行为的集成测试。单独测试方法并不意味着您必须测试实现而不是行为。 – Andrew

0

有些时候,我的回调中有if条件,在这种情况下我使用run_callbacks

before_save :parse_time, :if => Proc.new{ |post| post.foo == 'bar' } 

post = Post.new 
post.foo = 'bar' 
expected_time = Chronic.parse(post.time_string) 
post.run_callbacks :before_save 
assert_equal(post.time, expected_time) 

和带负正测试通过

post = Post.new 
post.foo = 'wha?' 
post.run_callbacks :before_save 
assert_nil(post.time) 

the APIa blog的更多细节。