Posted by
ngty
on
July 26, 2010
Using declaratives (eg. acts_as_authentic) provided by external libs is unavoidable in building a rails application. But testing 3rd party libs is surely not part of our work, yet how do we rest assured that these declaratives are called ??
Rule of thumb, never ever test 3rd party (external) libs, unless u suspect the lib is buggy, and want to prove to the lib author(s). Yet, we need to have a way to ensure the declaratives are called. Here's what i do to stike a balance:
# app/models/user.rb
model User < ActiveRecord::Base
acts_as_authentic
end
# spec/models/user_spec.rb
describe User do
include RailsBestPractices::Macros
should_act_as_authentic
end
Building upon wat we already have in write ur own spec macros:
# spec/macros.rb
module RailsBestPractices::Macros::ClassMethods
def should_act_as_authentic
should_include_module 'should be acting as authentic', /Authlogic::ActsAsAuthentic::/
end
private
def should_include_module(description, module_name_or_regexp)
# Calling of 3rd party declarative usually have the side effect of including extra module(s)
# from the lib, we want to ignore the ones included by a basic model (with no declaratives called),
# and grab only those extra modules, specific for the context class
basic_included_modules = Class.new(ActiveRecord::Base).included_modules
klass_included_modules = context_klass.included_modules
extra_modules = (klass_included_modules - basic_included_modules).map(&:to_s)
it(description) do
case module_name_or_regexp
when Regexp
extra_modules.any?{|mod| mod =~ module_name_or_regexp }.should be_true
else
extra_modules.should include(module_name_or_regexp.to_s)
end
end
end
end

Comments
1. One your tests have to know internal implementation details of the AuthLogic gem. If they change these internals it may break your tests even though everything is still perfectly working. So you end up with false positives and have to keep updating your code to track the internal implementation details of another gem.
2. What are you really testing? That you typed 17 character correctly? Why not also test to ensure you typed the correct characters in your database.yml, or make sure that ActiveRecord::Base really is the parent of User? As code becomes declarative the need for testing diminishes. For example a simple "has_many :users" is purely declarative and really doesn't need any testing. But if you give a "has_many" with complicated conditions and perhaps with a block of method to mixin then it stops being declarative and becomes more procedural and therefore might need some testing.
3. Look at all the code you had to write to "test" this single line of code. Does your test really verify anything. Your test code is 100x more complicated than the actual code it is testing. So how does that provide verification. We have to take it on faith that our test code is correct (obviously we can watch it fail, then watch it succeed to give us some assurance but that still doesn't prove it is correct). Therefore our test code should be simpler than the code that it is testing. The test code should be simple enough that we can say "I think my test code is correct because it is simple. I have reasonable confidence in it by visually inspecting it. I also saw it fail then saw it succeed. Assuming my test code is correct then therefore my more complicated application logic must be correct". If your tests are more complicated than the code it is testing you might as well throw it out as it requires less faith to assume the actual code is working than it requires to assume your test code is working. The point of having test code is to reduce the amount of faith in your code by only having to trust more simple code then letting that verify more complex code.
Not trying to rag on you too much. But this site is about best practices and I don't feel this is one. Just my two cents.
http://pastie.org/1060781
My point is all you are doing is mirroring your code and adding a lot of complication. If you can't trust yourself to write "acts_as_authentic" correctly how can you trust yourself to write "should_acts_as_authentic" and all that extra code to implement the should_* macro.
I do understand about the BDD/TDD way (fail first and then pass) but you are not really proving anything here as your test to verify the failure and success is more complicated than the code you are testing. How do you know your tests are failing and then passing for the right reason. Because of all the complication in the test code it could be failing and passing for some other reason. The only want to know if have faith in your test code. I would rather simply and straight forward test code to have faith in instead of complicated test code. If I cannot make my test code any simpler than the real code there is no benefit to writing the test code.
Just my two cents. :)
having a shoulda-like macro at the unit level (just making sure declaration is made), PLUS
testing a subset of the behaviours offered by the acts_as_* at the integration level
Obviously we have a different philosophy for testing and if that works for you great. I really just wanted to note that not everybody this this is a best practice and people shouldn't take all this "shoulda" stuff as a golden path to good code. IMHO it is just adding bulk to your tests without really proving anything taking up valuable developer time that could be spent on doing something useful in a project (not to mention it increases runtime of your tests).
If you can't describe a meaningful scenario where a class had good coverage by TDD standards, and the removal of a given module did NOT cause at least one of those tests to fail, then what is the practical benefit of this sort of test assertion?