Giter Club home page Giter Club logo

savon_spec's Introduction

Savon::Spec Build Status

Savon v1 Rspec Helpers

Deprecated

Starting in Savon v2, spec helpers are included in Savon itself. This gem is only helpful if you're using Savon v1. It is highly recommended to use Savon v2 with the Savon::SpecHelper module for new projects.

Installation

Savon::Spec is available through Rubygems and can be installed via:

$ gem install savon_spec

Expects

Include the Savon::Spec::Macros module into your specs:

RSpec.configure do |config|
  config.include Savon::Spec::Macros
end

By including the module you get a savon method to mock SOAP requests. Here's a very simple example:

let(:client) do
  Savon::Client.new do
    wsdl.endpoint = "http://example.com"
    wsdl.namespace = "http://users.example.com"
  end
end

before do
  savon.expects(:get_user)
end

it "mocks a SOAP request" do
  client.request(:get_user)
end

This sets up an expectation for Savon to call the :get_user action and the specs should pass without errors. Savon::Spec does not execute a POST request to your service, but uses Savon hooks to return a fake response:

{ :code => 200, :headers => {}, :body => "" }

To further isolate your specs, I'd suggest setting up FakeWeb to disallow any HTTP requests.

With

Mocking SOAP requests is fine, but what you really need to do is verify whether you're sending the right parameters to your service.

before do
  savon.expects(:get_user).with(:id => 1)
end

it "mocks a SOAP request" do
  client.request(:get_user) do
    soap.body = { :id => 1 }
  end
end

This checks whether Savon uses the SOAP body Hash you expected and raises a Savon::Spec::ExpectationError if it doesn't.

Failure/Error: client.request :get_user, :body => { :name => "Dr. Who" }
Savon::Spec::ExpectationError:
  expected { :id => 1 } to be sent, got: { :name => "Dr. Who" }

You can also pass a block to the #with method and receive the Savon::SOAP::Request before the POST request is executed.
Here's an example of a custom expectation:

savon.expects(:get_user).with do |request|
  request.soap.body.should include(:id)
end

Returns

Instead of the default fake response, you can return a custom HTTP response by passing a Hash to the #returns method.
If you leave out any of these values, Savon::Spec will add the default values for you.

savon.expects(:get_user).returns(:code => 500, :headers => {}, :body => "save the unicorns")

Savon::Spec also works with SOAP response fixtures (simple XML files) and a conventional folder structure:

~ spec
  ~ fixtures
    ~ get_user
      - single_user.xml
      - multiple_users.xml
  + models
  + controllers
  + helpers
  + views

When used inside a Rails 3 application, Savon::Spec uses Rails.root.join("spec", "fixtures") to locate your fixture directory.
In any other case, you have to manually set the fixture path via:

Savon::Spec::Fixture.path = File.expand_path("../fixtures", __FILE__)

Directory names inside the fixtures directory map to SOAP actions and contain actual SOAP responses from your service(s).
You can use one of those fixtures for the HTTP response body like in the following example:

savon.expects(:get_user).with(:id => 1).returns(:single_user)

As you can see, Savon::Spec uses the name of your SOAP action and the Symbol passed to the #returns method to navigate inside
your fixtures directory and load the requested XML files.

Never

Savon::Spec can also verify that a certain SOAP request was not executed:

savon.expects(:get_user).never

RSpec

This library is optimized to work with RSpec, but it could be tweaked to work with any other testing library.
Savon::Spec installs an after filter to clear out its Savon hooks after each example.

savon_spec's People

Contributors

fcheung avatar greyblake avatar msolli avatar rubiii avatar thomasjachmann avatar tjarratt avatar troszok avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

savon_spec's Issues

Savon.expects action namespacing issue

When doing savon.expects on actions that are namespaced the tests looks for the namespaced attribute rather the action.

Rspec test.

it "has private method login which which makes soap call and returns token" do
  stub_request(:get, "http://wcf.emarketingsaas.com/Sys.svc?wsdl").to_return(:status => 200, :body => "", :headers => {})
  savon.expects("Login").returns("../../epicore_email_service/login/login_response.xml")
 @class.send(:login).should == "eb910988-2820-4dbe-aca6-1f9610bfbf8c"
end

Rspec output.

Failure/Error: @class.send(:login).should == "eb910988-2820-4dbe-aca6-1f9610bfbf8c"
 Savon::Spec::ExpectationError:
   expected :Login to be called, got: {"xmlns"=>"http://wcf.spinnakerpro.net/1.0"}
 # /Users/BillWatts/Sites/ubn-webservices/services/epicore_email_service.rb:126:in `login'
 # ./epicore_email_service_spec.rb:36:in `block (2 levels) in <top (required)>'

Example of how the request is set up.

soap_client = Savon::Client.new do
  wsdl.endpoint = _self.instance_variable_get(:@credentials)["login_wsdl"]
  wsdl.namespace = "http://wcf.spinnakerpro.net/1.0/Sys/Login"
end

response = soap_client.request "Login" do
  http.headers['soapAction'] = "http://wcf.spinnakerpro.net/1.0/Sys/Login"
  soap.input = ["Login", {"xmlns" => "http://wcf.spinnakerpro.net/1.0"}]
  soap.body = {
    "Organization" => _self.instance_variable_get(:@credentials)["org"],
    "User" => _self.instance_variable_get(:@credentials)["username"],
    "Password" => _self.instance_variable_get(:@credentials)["password"]
  }
end

Example of how Savon renders the xml.

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:wsdl="http://wcf.spinnakerpro.net/1.0" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
  <env:Body>
    <Login xmlns="http://wcf.spinnakerpro.net/1.0">
      <Organization></Organization>
      <User></User>
      <Password></Password>
    </Login>
  </env:Body>
</env:Envelope>

I think this is a bug, but it could be poor wsdl design on the remote end or something I'm doing incorrectly.

README example code references undefined things

I stumbled upon this gem and started using the README. As far as I can see the example code relies on things that are not part of the code there. I was confused with these:

  • User.client is not defined
  • how :multiple_users and :single_user responses become User objects
    • these come from fixture I think so the example code should also have the fixture files

[FEATURE REQUEST] Test the number of request

Hi,

It can be good, to test the number of this request like mock library can do. By exemple :

savon.expects(:get_user).once
savon.expects(:get_user).twice
savon.expects(:get_user).at_least(x)
savon.expects(:get_user).exactly(x)
savon.expects(:get_user).at_most(x)

Documentation using Fakeweb

In the README

"To further isolate your specs, I'd suggest setting up FakeWeb to disallow any HTTP requests."

But could you please describe how to do this!

Is it like this?

FakeWeb.allow_net_connect = false

Inconsistency with expectations due to the implementation of Savon hooks

Let me detail an interesting problem I just encountered, though I am not sure of the best way to go about correcting it. This issue touches on both savon and savon_spec, but I encountered it while using savon_spec, so I will document it here.

A basic summary is that there is inconsistent behaviour when using Savon::Spec::Mock#expects because of how Savon itself implements hooks. If in my first test I initialize a Savon client using Savon::Client::new and then add an expectation using savon.expects, my expectation will be ignored, and Savon will normally process my next SOAP request. However, on all subsequent tests, if I follow the same workflow (initialize a client, then add an expectation), my expectation will not be ignored, and my next SOAP request will be handled by the mock. This behaviour is inconsistent, and it was very confusing for me to debug.

This occurs because of how hooks are implemented in Savon. The first client I initialize will clone Savon::config. Initially, @hooks in Savon::config will be nil, so this instance variable will not be copied to my clone of Savon::config. When I set an expectation using savon.expects, the hooks are added to @hooks in Savon::config. These changes are not, however, seen by the client I have already initialized. Therefore, when I call request on my client, it will make an HTTP request.

Now let's assume I follow the exact same workflow for my next test. The second client I initialize will clone Savon::config, as before. However, this time, @hooks in Savon::config will not be nil. It will be a hook group with no hooks. This is because @hooks was initialized with a hooks group in my first test when I created an expectation. Therefore, my second client will copy the @hooks instance variable to its config object. Because clone only does a shallow copy, @hooks in Savon::config and @hooks in the second client are the same object. Therefore, when I call savon.expects to set up an expectation and it adds hooks to @hooks in Savon::config, it will be seen by the second client. Thus, when I call request on my client, the hooks will properly intercept my call, and the expectation will be triggered.

Here is an example of this: https://gist.github.com/3305938

Actually, it is not even required to create an expectation in the first test I run. If I run any example before my second example, even if this first example does not use Savon in any way, the second test will succeed. Why? Because Savon::Spec installs a hook that runs after each RSpec example. This hook's purpose is to clears out all the hooks currently installed in Savon. In doing this clearing, it initializes @hooks in Savon::config, thus creating the conditions that will allow the second test to succeed. Confusing, isn't it? :-)

This leads to me a few issues:

1 - What is the desired behaviour of Savon hooks? If you instantiate a number of clients before @hooks in Savon::config has a value, and then instantiate a number of clients once it does have a value, the hooks are seen by the latter group but not the former. This is inconsistent and confusing behaviour. It seems to me that hooks should be seen by all Savon clients, unless there is an explicit desire for a client to ignore hooks or use its own group of hooks.

2 - The examples in the docs only ever show the client being lazy initialized using a let block in rspec. This allows the example tests to work fine, because the client will not be initialized until after the expectation has been created. However, lazy initialization is very often not a viable option. I inject my Savon client into my object under test before the commencement of each example. I think is a fairly common pattern which will not work consistently with the current implementation. There is a simple workaround however: I just call Savon::config.hooks in my before(:all) block. When I call Savon::config.hooks, it initializes @hooks, and therefore the first client I create (and all subsequent clients) will have @hooks in their config objects set to the same object as @hooks in Savon::config. Obviously, this is not an ideal solution.

I'm not sure of the author's intention, but it seems that the hooks implementation is currently not very well defined in its behaviour. I won't submit a pull request, because I am not sure how the author desires hooks to work. I think that a hook group should be common across all clients. Either way, it doesn't make sense to make a hook group shared across only clients that have been created after a hook group has already been instantiated in Savon::config.

I look forward to your comments.

Regards,

Tim Abdulla

Please provide example of XML file with SOAP response

I currently put the following in an xml file, but somehow it doesn't seem to work with savon_spec.
Should I only put the part inside the <soapenv:Body> ? or what?

<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Body>
      <soapenv:Fault>
        <faultcode>soapenv:Server</faultcode>
        <faultstring>{ 'INVALID_INPUT' }</faultstring>
        <detail>
            <Exception>org.apache.axis2.AxisFault: { 'INVALID_INPUT' }</Exception>
        </detail>
      </soapenv:Fault>
   </soapenv:Body>
</soapenv:Envelope>

Using Savon for request test of my Rails SOAP actions

Hi,
i need to do Request RSpec testing of SOAP actions in my Rails app.
Using Savon as klient seems nice, but I cannot set the correct path/uri to WSDL inside tests. These URIs do not work:

 wsdl=   "http://localhost:3000/soap/wsdl"
 wsdl = "http://www.example.com/soap/wsdl"
 wsdl=   "/soap/wsdl"
 @client = Savon.client(:wsdl => wsdl )

For more complex question see http://stackoverflow.com/questions/17185639/integration-request-rspec-test-of-my-soap-service.

Is there any way to do this?

Documentation to use it with another test tool

Hi,

I tried with so many forms to mock/stub a savon request with Test::Unit, and I can't got this work until present moment. So, please, bring up some documentation about how to test in Test::Unit using webmock or fakeweb.

Savon.expects doesn't work if there are more than one expectations in a spec

Hi Daniel,

if i write a spec which has two savon expectations i get

Savon::Spec::ExpectationError:
       expected :second_action to be called, got: :first_action

if i change the expectations order i get this

Savon::Spec::ExpectationError:
       expected :first_action to be called, got: :second_action

WHY, whats happend from savon_spec 0.9.* to 1.3.* ??

before do 
  savon.expects(:first_action).returns(:simple)
  savon.expects(:second_action).returns(:alina)
end

it "should bla" do
  call something with first calls first_action and then second action
end

Spec failures with savon >= 0.9.3

With the latest savon (currently 0.9.6), savon_spec no longer works properly. Here is the output for "rspec spec":

Failures:

  1) Savon::Spec::Mock#expects and #with should expect Savon to send a given SOAP body
     Failure/Error: client.request :get_user do
     Mocha::ExpectationError:
       unexpected invocation: #<AnyInstance:Savon::SOAP::XML>.body=(nil)
       unsatisfied expectations:
       - expected exactly once, not yet invoked: HTTPI.post()
       - expected exactly once, not yet invoked: #<AnyInstance:Savon::SOAP::XML>.body=(:id => 1)
     # ./spec/savon/spec/mock_spec.rb:47

  2) Savon::Spec::Mock#expects and #with should fail when the SOAP body was not send
     Failure/Error: client.request(:get_user)
     Mocha::ExpectationError:
       unexpected invocation: #<AnyInstance:Savon::SOAP::XML>.body=(nil)
       unsatisfied expectations:
       - expected exactly once, not yet invoked: HTTPI.post()
       - expected exactly once, not yet invoked: #<AnyInstance:Savon::SOAP::XML>.body=(:id => 1)
     # ./spec/savon/spec/mock_spec.rb:53

Finished in 0.03766 seconds
29 examples, 2 failures

Failed examples:

rspec ./spec/savon/spec/mock_spec.rb:46 # Savon::Spec::Mock#expects and #with should expect Savon to send a given SOAP body
rspec ./spec/savon/spec/mock_spec.rb:52 # Savon::Spec::Mock#expects and #with should fail when the SOAP body was not send

savon.expects(:action) not working

When I don't specify a return a value savon_spec fails calling code for nil.

#<NoMethodError: undefined method `code' for nil:NilClass>
"/Users/betelgeuse/.rvm/gems/ruby-1.9.3-p125/gems/savon-0.9.7/lib/savon/soap/request.rb:52:in `with_logging'",
 "/Users/betelgeuse/.rvm/gems/ruby-1.9.3-p125/gems/savon-0.9.7/lib/savon/soap/request.rb:31:in `response'",
 "/Users/betelgeuse/.rvm/gems/ruby-1.9.3-p125/gems/savon-0.9.7/lib/savon/client.rb:79:in `request'",

Can't test against a class which extends Savon::Model

It appears that you can't test against a class which extends Savon::Model.

I have written savon_model_spec_fail.rb to demonstrate that instead of mocking the get_user SOAP action, the call is passed on to httpclient:

$ rspec savon_model_spec_fail.rb
.W, [2012-07-16T23:15:31.268851 #22655]  WARN -- : HTTPI executes HTTP GET using the httpclient adapter
FW, [2012-07-16T23:15:32.375432 #22655]  WARN -- : HTTPI executes HTTP GET using the httpclient adapter
F

Failures:

  1) with model mocks a SOAP request for an instance method
     Failure/Error: client.request(:get_user)
     SocketError:
       getaddrinfo: nodename nor servname provided, or not known (http://service.example.com:80)
     # ./savon_model_spec_fail.rb:32:in `get_user_class'
     # ./savon_model_spec_fail.rb:47:in `block (2 levels) in <top (required)>'

  2) with model mocks a SOAP request for an class method
     Failure/Error: client.request(:get_user)
     SocketError:
       getaddrinfo: nodename nor servname provided, or not known (http://service.example.com:80)
     # ./savon_model_spec_fail.rb:36:in `get_user_instance'
     # ./savon_model_spec_fail.rb:51:in `block (2 levels) in <top (required)>'

Finished in 1.19 seconds
3 examples, 2 failures

Failed examples:

rspec ./savon_model_spec_fail.rb:46 # with model mocks a SOAP request for an instance method
rspec ./savon_model_spec_fail.rb:50 # with model mocks a SOAP request for an class method

License file

Hello,

I was wondering if you would be able to add a license file to the project so we would know under what license we're able to use this code?
Thank a lot in advance!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.