[rspec-users] stubbing /mocking ldap within a method

J. B. Rainsberger me at jbrains.ca
Tue Jun 21 17:56:42 EDT 2011


On Tue, Jun 21, 2011 at 17:00, Jed Schneider <jed.schneider at gmail.com> wrote:
> given I have the following with the internal assignments to filter and ldap:
> class Collaborator
>   def self.search_ldap(term)
>     last_name = term.strip.split(/[, ]/).first.downcase
>     filter = Net::LDAP::Filter.eq('surName', last_name)
>     ldap = Net::LDAP.new( :host => 'foo', :port => 636, :base => 'bar',
> :encryption => :fiz)
>     ldap.search(:filter => filter)
>   end
> end
> I would like mock out the LDAP class so i can test the behavior of the
> method without LDAP.

In this case, I'd invoke the rule "Don't mock types you don't own".

What can you trust? LDAP does the right thing.
What can you get wrong? Sending the wrong parameters to LDAP.
Therefore, check the parameters you send to LDAP.

class Collaborator
  def initialize(ldapGateway = LdapGateway.new)
    @gateway = ldapGateway
  end

  def self.search_ldap(term)
    last_name = term.strip.split(/[, ]/).first.downcase
    @gateway.search('surName', last_name, 'foo', 636, 'bar', :fiz)
  end
end

class LdapGateway
  def search(label, value, host, port, base, encryption)
    filter = Net::LDAP::Filter.eq('surName', last_name)
    ldap = Net::LDAP.new( :host => 'foo', :port => 636, :base =>
'bar', :encryption => :fiz)
    ldap.search(:filter => filter)
  end
end

Judging only by the names, "label" and "value" seem separate from
"host"…"encryption". My intuition tells me to move the latter group
into constructor parameters.

class Collaborator
  def initialize(ldapGateway)
    @gateway = ldapGateway
  end

  def self.search_ldap(term)
    last_name = term.strip.split(/[, ]/).first.downcase
    @gateway.search('surName', last_name)
  end
end

class LdapGateway
  def initialize(host, post, base, encryption)
    # assign to fields
  end

  def search(label, value)
    filter = Net::LDAP::Filter.eq('surName', last_name)
    # SMELL Temporal duplication; can we create an LDAP in the constructor?
    ldap = Net::LDAP.new( :host => @host, :port => @port, :base =>
@base, :encryption => @encryption)
    ldap.search(:filter => filter)
  end
end

Now the LdapGateway responds to search(label, value), so I don't think
Collaborator needs to know that any LDAP is going on.

class Collaborator
  def initialize(term_repository)
    @term_repository = term_repository
  end

  def self.search_ldap(term)
    last_name = term.strip.split(/[, ]/).first.downcase
    @term_repository.search('surName', last_name)
  end
end

I'm using the mechanical name "Repository" here, because I don't yet
understand the role this object plays in the system.

class LdapGateway # I'm a TermRepository! Extract a module someday?
  def initialize(host, post, base, encryption)
    # assign to fields
  end

  def search(label, value)
    filter = Net::LDAP::Filter.eq('surName', last_name)
    # SMELL Temporal duplication; can we create an LDAP in the constructor?
    ldap = Net::LDAP.new( :host => @host, :port => @port, :base =>
@base, :encryption => @encryption)
    ldap.search(:filter => filter)
  end
end

Now it's easy to check:

describe Collaborator do
  context "when searching" do
    it "should pass the proper surname to the TermRepository" do
      termRepository = mock("a TermRepository")
      termRepository.should_receive(:search).with('surName', 'rainsberger')

      Collaborator.new(termRepository).search('J. B. Rainsberger')
    end

    it "should return the repository's results" do
      Collaborator.new(stub(:search => ["a", "b", "c"])).search('J. B.
Rainsberger').should == ["a", "b", "c"]
    end
  end
end

Does that help you?
-- 
J. B. (Joe) Rainsberger :: http://www.jbrains.ca ::
http://blog.thecodewhisperer.com
Author, JUnit Recipes
Free Your Mind to Do Great Work :: http://www.freeyourmind-dogreatwork.com


More information about the rspec-users mailing list