I know the questioned are directed towards Dan so I hope you don't me chiming in. My comments are inline.
On Dec 15, 2007 2:17 AM, Pat Maddox <[EMAIL PROTECTED]> wrote: > > On Dec 8, 2007 4:06 AM, Dan North <[EMAIL PROTECTED]> wrote: > > I prefer the mantra "mock roles, not objects", in other words, mock things > > that have behaviour (services, components, resources, whatever your > > preferred term is) rather than stubbing out domain objects themselves. If > > you have to mock domain objects it's usually a smell that your domain > > implementation is too tightly coupled to some infrastructure. > > Assuming you could easily write Rails specs using the real domain > objects, but not hit the database, would you "never" mock domain > objects (where "never" means you deviate only in extraordinary > circumstances)? I'm mostly curious in the interaction between > controller and model...if you use real models, then changes to the > model code could very well lead to failing controller specs, even > though the controller's logic is still correct. In Java you don't mock domain objects because you want to program toward an interface rather then a single concrete implementation. Conceptually this still applies in Ruby, but because of differences between the languages it isn't a 1 to 1 mapping in practice. With regard to controllers and models in Rails, I don't want my controller spec to use real model objects. The requirement of a model existing can be found as part of the discovery process for what a controller needs to do its job. If the implementation of a model is wrong it isn't the job of the controller spec to report the failure. It's the job of the model spec or an integration test (if its an integration related issue) to report the failure. When you make it a job of the controller spec to ensure that the real model objects work correctly within a controller it is usually because there is a lack of integration tests and controller specs are being used to fill in the void. Also, controllers can achieve a better level of programming toward the "interface" rather then a concrete class by using dependency injection. For example consider using lighight DI using the injection plugin (http://atomicobjectrb.rubyforge.org/injection/): class PhotosController < ApplicationController inject :project_repository def index @projects = @project_repository.find_projects end end There is a config/objects.yml file which exists to define what project_repository is: --- project_repository: use_class_directly: true class: Project This removes any unneeded coupling between the controller and the model. Although the most common thing I've seen in Rails is to partial mock the Project class in your spec. Although this works there is unnecessary coupling between your controller a concrete model class. > > > What is your opinion on isolating tests? Tests should be responsible for ensuring an object works as expected. So it's usually a good thing to isolate objects under test to ensure that they are working as expected. If you don't isolate then you end up with a lot of little integration tests. Now when one implementation is wrong you get 100 test failures rather then 1 or 2, which can be a headache when you're trying to find out why something failed. > Do you try to test each > class in complete isolation, mocking its collaborators? Yes. The pattern I find I follow in testing is that objects whose job it is to coordinate or manage other objects (like controllers, presenters, managers, etc) are always tested in isolation. Interaction-based testing is the key here. These objects can be considered branch objects. They connect to other branches or to leaf node objects. Leaf node objects are the end of the line and they do the actual work. Here is where I use state based testing. I consider ActiveRecord models leaf nodes. A practice that I've been following that was inspired from a coworker has been that an object should be a branch or a leaf, but not both. Most Rails applications don't follow anything like this and it's common to find bloated controllers and bloated models (most people IMO do not understand the Skinny Controller, Fat Model post, bloated models are now becoming an up and coming trend unfortunately). Objects and methods built-in the language, standard library or framework are exempt from my above statements. If a manager coordinates return values from methods being called other objects and pushes them onto an array, I don't mock a call to Array.new. > When you use > interaction-based tests, do you always leave those in place, or do you > substitute real objects as you implement them (and if so, do your > changes move to a more state-based style)? Leave the mocked out collaborators in place. An interaction based test verifies that the correct interaction takes place. As soon as you remove the mock and substitute it with a real object your test has become compromised. It's no longer verifying the correct interaction occurs, it now only makes sure your test doesn't die with a real object. If you do substitute in a real object, the only way you would be able to maintain the integrity of the test is to partial mock your real object to expect the right methods to be called. This will ensure that the interaction continues to take place. But what happens is that the test gets muddied up with things that don't need to be there. > How do you approach > specifying different layers within an app? One way to think about this is is terms of composition and inheritance. When layers interact using composition you treat it and test it differently then if you use inheritance. For example a ProjectsController using a @project_repository (see injection example above) or a Project model subclassing ActiveRecord::Base. I need to think about them some more though. > Do you use real > implementations if there are lightweight ones available, or do you > mock everything out? For me it depends. With most Rails projects I've worked on there has been one suite of integration tests against the application as a whole and then a bunch of unit tests. The times this has differed are when the application relied on third-party services. These services would be replaced with dummy or lightweight implementations for my integration tests (for example geocoding). Although there would be another set of integration tests to specifically test our app against the actual service. An integration test should test that real objects are working together correctly produce the intended system behavior. You should never mock objects out at this level, but you may need to provide stub implementations for third party services. > > I realize that's a ton of questions...I'd be very grateful (and > impressed!) if you took the time to answer any of them. Also I'd love > to hear input from other people as well. > It's too bad we can't just stand at a whiteboard and talk this out. The answers to these questions could fill a book and email hardly does it justice to provide clear, coherent and complete answers. Not that my response are "answers" to your questions, but it's how I think about testing and TDD. -- Zach Dennis http://www.continuousthinking.com http://www.atomicobject.com _______________________________________________ rspec-users mailing list rspec-users@rubyforge.org http://rubyforge.org/mailman/listinfo/rspec-users