Code Monkey home page Code Monkey logo

bypass's Introduction

Bypass

Build Status Module Version Hex Docs Total Download License Last Updated

Bypass provides a quick way to create a custom plug that can be put in place instead of an actual HTTP server to return prebaked responses to client requests. This is most useful in tests, when you want to create a mock HTTP server and test how your HTTP client handles different types of responses from the server.

Bypass supports Elixir 1.10 and OTP 21 and up. It works with Cowboy 2.

Usage

To use Bypass in a test case, open a connection and use its port to connect your client to it.

If you want to test what happens when the HTTP server goes down, use Bypass.down/1 to close the TCP socket and Bypass.up/1 to start listening on the same port again. Both functions block until the socket updates its state.

Expect Functions

You can take any of the following approaches:

  • expect/2 or expect_once/2 to install a generic function that all calls to bypass will use
  • expect/4 and/or expect_once/4 to install specific routes (method and path)
  • stub/4 to install specific routes without expectations
  • a combination of the above, where the routes will be used first, and then the generic version will be used as default

Example

In the following example TwitterClient.start_link() takes the endpoint URL as its argument allowing us to make sure it will connect to the running instance of Bypass.

defmodule TwitterClientTest do
  use ExUnit.Case, async: true

  setup do
    bypass = Bypass.open()
    {:ok, bypass: bypass}
  end

  test "client can handle an error response", %{bypass: bypass} do
    Bypass.expect_once(bypass, "POST", "/1.1/statuses/update.json", fn conn ->
      Plug.Conn.resp(conn, 429, ~s<{"errors": [{"code": 88, "message": "Rate limit exceeded"}]}>)
    end)

    {:ok, client} = TwitterClient.start_link(url: endpoint_url(bypass.port))
    assert {:error, :rate_limited} == TwitterClient.post_tweet(client, "Elixir is awesome!")
  end

  test "client can recover from server downtime", %{bypass: bypass} do
    Bypass.expect(bypass, fn conn ->
      # We don't care about `request_path` or `method` for this test.
      Plug.Conn.resp(conn, 200, "")
    end)

    {:ok, client} = TwitterClient.start_link(url: endpoint_url(bypass.port))

    assert :ok == TwitterClient.post_tweet(client, "Elixir is awesome!")

    # Blocks until the TCP socket is closed.
    Bypass.down(bypass)

    assert {:error, :noconnect} == TwitterClient.post_tweet(client, "Elixir is awesome!")

    Bypass.up(bypass)

    # When testing a real client that is using e.g. https://github.com/fishcakez/connection
    # with https://github.com/ferd/backoff to handle reconnecting, we'd have to loop for
    # a while until the client has reconnected.

    assert :ok == TwitterClient.post_tweet(client, "Elixir is awesome!")
  end

  defp endpoint_url(port), do: "http://localhost:#{port}/"
end

That's all you need to do. Bypass automatically sets up an on_exit hook to close its socket when the test finishes running.

Multiple concurrent Bypass instances are supported, all will have a different unique port. Concurrent requests are also supported on the same instance.

Note: Bypass.open/0 must not be called in a setup_all blocks due to the way Bypass verifies the expectations at the end of each test.

How to use with ESpec

While Bypass primarily targets ExUnit, the official Elixir builtin test framework, it can also be used with ESpec. The test configuration is basically the same, there are only two differences:

  1. In your Mix config file, you must declare which test framework Bypass is being used with (defaults to :ex_unit). This simply disables the automatic integration with some hooks provided by ExUnit.

    config :bypass, test_framework: :espec
  2. In your specs, you must explicitly verify the declared expectations. You can do it in the finally block.

    defmodule TwitterClientSpec do
      use ESpec, async: true
    
      before do
        bypass = Bypass.open()
        {:shared, bypass: bypass}
      end
    
      finally do
        Bypass.verify_expectations!(shared.bypass)
      end
    
      specify "the client can handle an error response" do
        Bypass.expect_once(shared.bypass, "POST", "/1.1/statuses/update.json", fn conn ->
          Plug.Conn.resp(conn, 429, ~s<{"errors": [{"code": 88, "message": "Rate limit exceeded"}]}>)
        end)
    
        {:ok, client} = TwitterClient.start_link(url: endpoint_url(shared.bypass.port))
        assert {:error, :rate_limited} == TwitterClient.post_tweet(client, "Elixir is awesome!")
      end
    
      defp endpoint_url(port), do: "http://localhost:#{port}/"
    end

Configuration options

Set :enable_debug_log to true in the application environment to make Bypass log what it's doing:

config :bypass, enable_debug_log: true

Installation

Add :bypass to your list of dependencies in mix.exs:

def deps do
  [
    {:bypass, "~> 2.1", only: :test}
  ]
end

We do not recommended adding :bypass to the list of applications in your mix.exs.

License

This software is licensed under the MIT license.

About

This project is maintained and funded by PSPDFKit.

Please ensure you signed our CLA so we can accept your contributions.

See our other open source projects, read our blog or say hello on Twitter (@PSPDFKit).

bypass's People

Contributors

alco avatar boris-kloeckner avatar bradhanks avatar bszaf avatar cmwilhelm avatar cschilbe avatar dkarter avatar ericmj avatar getong avatar gigitsu avatar gmile avatar hassox avatar josevalim avatar kianmeng avatar kpanic avatar leifg avatar lnikkila avatar meleyal avatar milmazz avatar msch avatar nathanl avatar noozo avatar ream88 avatar scrogson avatar sgerrand avatar stevedomin avatar thiamsantos avatar tompave avatar vitortrin avatar ybur-yug 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  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  avatar  avatar  avatar  avatar

bypass's Issues

Query string in the URL fails with Route error

If testing a URL like /foo.json?bar=baz&doo=1 this fails with:

** (exit) an exception was raised:
    ** (RuntimeError) Route error
        (bypass) lib/bypass/plug.ex:24: Bypass.Plug.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

However just /foo.json will work.

New version?

v0.5.1 was released long ago. There were several interesting additions since then.

Could you release a new version?

unable to test against verify_expectations/1

Description:

  • unable to test against verify_expectations

Just wondering if there is some way to allow testing manually

When the test exits, the verify_expectations/1 function automatically calls on_exit..... and then I get the error No HTTP request arrived at Bypass and the test fails. The thing is that this is the behaviour I am expecting from one test in a suite of tests and I want to assert that this happened.

Is there some way to catch the error in the exit and assert in the test that this is the expected behaviour?

Or alternatively, is there some way that verify_expectations/1 being run in the on_exit function could be disabled manually and run inside the test instead?

Running the function manually is also not possible at the moment as the function raises an error Not available in ExUnit, as it's configured automatically.

remove dependency to ExUnit

Hey,

First off thanks for the library its useful, good work. I was wondering if you could remove the dependency on ExUnit, I and several others use https://github.com/antonmi/espec as my testing framework and the ExUnit.Callbacks.on_exit check you do obviously blows up for me as I have no process running for ExUnit.

I understand the value this callback and see your verifying calls here and even if the dependency remained it would be nice if this was an explicit call and not wired into the ExUnits on_exit calback

spurious {:error, :eaddrinuse} with Bypass.open(port: fixed_port)

As in #31, I get {:error, :eaddrinuse} running a sequential test suite (async: false), where each test starts a Bypass listening on the same fixed port.

I see that in L271 you try to do a sync socket close, but it seems that this is not enough, maybe the port is sync released but the underlying OS socket is still in use.

To reproduce the issue we need an async: false test suite that stress this condition, just a test like THIS repeated over and over till the condition happens.

I hope in the next week to find some spare time to write a test and get the error on a travis build.

Allow the redefinition of routes

Often my application needs to send multiple requests to a single route in a single test. As of this version, I need to declare a route with a Bypass.expect/4 that matches all the requests, and this quickly becomes a bloated match. Mox allows you to add multiple expectations, that leads to simple code. I'm not asking for expectations to be as complete as Mox, but allowing to redefine the route would be great.

This looks like to be simple, it should be a matter of making this match

updated_expectations =
case Map.get(expectations, route, :none) do
:none ->
Map.put(expectations, route, new_route(
fun,
case expect do
:expect -> :once_or_more
:expect_once -> :once
:stub -> :none_or_more
end
))
_ ->
raise "Route already installed for #{method}, #{path}"
end

into

 updated_expectations = 
       Map.put(expectations, route, new_route( 
         fun, 
         case expect do 
           :expect -> :once_or_more 
           :expect_once -> :once 
           :stub -> :none_or_more 
         end 
       ))

Support new Plug 1.1.x

Plug has a new version, I think it's still compatible with bypass but current mix.exs forbids it:

$ mix deps.get
Running dependency resolution
Conflict on plug
  From mix.lock: 1.1.0
  From bypass 0.1.0: ~> 1.0.0

** (Mix) Hex dependency resolution failed, relax the version requirements or unlock dependencies

Specifying port for new bypass instance

Hey, we've been working on standing up an Elixir application and found bypass a few days ago (great work, it's been very helpful so far!).

I was curious to know if there's been any interest / discussion about allowing users to specify a desired port to open a new Bypass on? We're using some libs that wrap http clients, but bake in their config at build time so the Application.put_env() model in your examples is a no-go for passing in the port Bypass.open assigns.

OTP21: not compiling due to meck dependency

Hello,

Compiled src/meck_proc.erl
src/meck_code_gen.erl:185: erlang:get_stacktrace/0: deprecated; use the new try/catch syntax for retrieving the stack backtrace
Compiling src/meck_code_gen.erl failed:
ERROR: compile failed while processing /tmp/bypass/deps/meck: rebar_abort```

It seems that the `mix.lock` meck version is `0.8.4`, should be `0.8.12`

Running `mix deps.update espec` should do the trick.

Support new ranch transport options format

Cowboy 2.5.0 depends on Ranch 1.6.2 : https://hex.pm/packages/cowboy/2.5.0
Ranch 1.6.0 changed transport options to be a map and hard-deprecated the keyword format with a warning, which shows up in tests that are using Bypass:

Setting Ranch options together with socket options is deprecated. Please use the new map syntax that allows specifying socket options separately from other options.

See commit and a note in migrate guide:

Ranch-specific transport options and socket options are now better separated. When passing Ranch-specific transport options, Ranch now expects to receive a map, in which case socket options are passed in the socket_opts value. When there are only socket options they can be passed to Ranch directly as a convenience.

I found that Bypass passes the transport options here and I think to get rid of this warning and support Ranch 2.0 (so also future Cowboy versions), bypass needs to pass a map instead of a keyword list there (map keys are also slightly different).

I'll try to figure out a way to support both formats and send a PR

Running concurrent tests

Hi,

I've followed your Readme example and realised that it's either not possible or not explained whether it is possible to run tests concurrently with bypass. The following is an example:

defmodule MyTest do
  use ExUnit.Case, async: true

  setup do 
    bypass = Bypass.open
    Application.put_env(:my_app, :hostname, "http://localhost:#{bypass.port}"
    {:ok, bypass: bypass}
  end

  test "a test", %{bypass: bypass} do
    ...
  end

  test "another test", %{bypass: bypass} do
    ...
  end
end

In this example, both test setups are updating Application concurrently. It seems it hasn't caused any issues in my tests yet though.

Am I missing something ? Is there a workaround or should I stop using async: true ?

Asserting 0 Requests

Is there a way with bypass to assert that NO http requests were made?

We have a test in which we want to assert that no requests were made to an api. We can get the normal error:

     No HTTP request arrived at Bypass
     stacktrace:
       (bypass) lib/bypass.ex:86: Bypass.do_verify_expectations/2

But we'd like the presence of that to be an assertion.

Is that possible?

Move primary documentation out of the README

The primary documentation seems to be in the readme instead of on hexdocs.

If there is interest, I can submit a PR that adds what is in the README to the Bypass @moduledoc via this pattern or move the docs for each function into their respective @doc blocks.

Let me know what you think!

Cheers ๐Ÿป

Version 0.7.0 breaks previous behaviour

Hello,

I recently upgraded one of my project using bypass 0.6 to 0.8 and it completely broke my test suite. It seems that having multiple expect/2 in the same test case is no longer supported.

Example:

defmodule Test do
  setup do
    bypass = Bypass.open()
    {:ok, bypass: bypass}
  end

  test "a test", %{bypass: bypass} do
    Bypass.expect bypass, fn conn ->
      assert "/email" == conn.request_path
      assert "POST" == conn.method
      Plug.Conn.resp(conn, 200, "ok")
    end
   
    Client.make_request()
  end

  test "a second test", %{bypass: bypass} do
    Bypass.expect bypass, fn conn ->
      assert "/email" == conn.request_path
      assert "POST" == conn.method
      Plug.Conn.resp(conn, 404, "not found")
    end

    Client.make_request()
  end
end

With this setup I get the following error:

** (RuntimeError) Route already installed for any, any
    (bypass) lib/bypass/instance.ex:129: Bypass.Instance.do_handle_call/3
    (stdlib) gen_server.erl:636: :gen_server.try_handle_call/4
    (stdlib) gen_server.erl:665: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

This seems like a backward-incompatible change and I haven't a found to make it work as it use to, any help would be appreciated. Thanks!

Route error on valid routes

Hi,

I get an Route error on routes with decimal values like /myroute/v1/0,10.0

Request: GET /myroute/v1/0,10.0
** (exit) an exception was raised:
    ** (RuntimeError) Route error
        (bypass) lib/bypass/plug.ex:24: Bypass.Plug.call/2
        (plug_cowboy) lib/plug/cowboy/handler.ex:12: Plug.Cowboy.Handler.init/2
        (cowboy) myroute/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
        (cowboy) myroute/deps/cowboy/src/cowboy_stream_h.erl:296: :cowboy_stream_h.execute/3
        (cowboy) myroute/deps/cowboy/src/cowboy_stream_h.erl:274: :cowboy_stream_h.request_process/3
        (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

OWM use these routes example here https://samples.openweathermap.org/pollution/v1/co/0.0,10.0/current.json?appid=b1b15e88fa797225412429c1c50c122a1

I'm not sure if this is a Plug specific issue.

[Proposal] Support multiple expectation blocks with routing and more

Background

Currently Bypass allows users to install a single expect handler that should be invoked one or more times to make the test containing it pass. The "one or more" semantics is not as strict as some people might expect.

Additionally, the current implementation cannot handle concurrent requests (see #12). This behaviour is not intentional and it is desirable to support concurrent requests in order to accommodate for such use cases that require this. After all, testing with Bypass should not require changes to the application code and it's easy to imagine an application that has this behaviour by default.

Another limitation that has been reported is the inability to specify multiple routes on a single Bypass instance, see #10. Supporting this functionality is also desirable in order to allow for testing more complex behaviours.

This proposal addresses those points by introducing several new functions.

expect/2 and expect/4

  • expect(bypass_instance, fn conn -> ... end)
  • expect(bypass_instance, <method> | [<method>, ...], <route>, fn conn -> ... end)

The first form works exactly the same as the existing function but it will support concurrent requests.

The second form allows installing multiple handlers so long as each one has a unique method-route combination, each of which has to be invoked at least once for the test to pass.

expect_once/2 and expect_once/4

  • expect_once(bypass_instance, fn conn -> ... end)
  • expect_once(bypass_instance, <method> | [<method>, ...], <route>, fn conn -> ... end)

The first form adds a handler that should be invoked exactly once for the test to pass.

The second form allows installing more than one handler as long as each one has a unique method-route combination. Each of the handlers has to be invoked exactly once in order for the test to pass.

Notes

  • Both expect/4 and expect_once/4 take one or more HTTP method names as strings. Passing a
    list means that requests that use any of the methods in the list will match.

    The <route> is a list of route segments. This allows for doing things like dynamic and
    wildcard segments without problem.

  • All expect/* functions will return a handler ref. This ref will be accepted in calls to
    pass, down, and up allowing to selectively disable or pass each individual handler. Passing
    the Bypass instance to any of those functions will continue to work and will affect all
    handlers belonging to that instance at once.

  • As a sample use case, by using the building blocks proposed here it is possible to build a state machine out of expectations by relying on an external storage for state. Here is an example using an Agent:

    import Bypass, except: [open: 0]
    import Plug.Conn, only: [resp: 3]
    
    test "multi-step request" do
      agent = Agent.start_link(fn -> 1 end)
      increment_fn = fn ->
        Agent.get_and_update(agent, fn step_no -> {step_no, step_no+1} end)
      end
    
      bypass = Bypass.open
      expect(bypass, "GET", ["foo"], fn conn ->
        case increment_fn.() do
          1 -> resp(conn, 404, "")
          4 -> resp(conn, 200, "")
          _ -> flunk "I didn't expect that"
        end
      end)
      expect_once(bypass, "GET", ["bar"], fn conn ->
        assert 2 == increment_fn.()
        resp(conn, 200, "")
      end)
      expect_once(bypass, "POST", ["foo"], fn conn ->
        assert 3 == increment_fn.()
        resp(conn, 200, "")
      end)
    
    
      HTTPClient.go(bypass.port)
    end

not working on travis?

I have some tests with bypass that work perfectly on localhost, but when running on travis many time I get this error:

** (FunctionClauseError) no function clause matching in Bypass.expect/2
stacktrace:
(bypass) lib/bypass.ex:38: Bypass.expect({:error, :eaddrinuse}, ...

Error raised when testing with an https outbound request

Hello! First of all I'd like to say I really like and appreciate Bypass!!! However, I'm running into an issue I'm having trouble resolving. I have a test that makes an https scheme outbound request using Bypass, which gives me this error:

11:31:03.775 [error] Cowboy returned 400 because it was unable to parse the request headers.

This may happen because there are no headers, or there are too many headers
or the header name or value are too large (such as a large cookie).

You can customize those values when configuring your http/https
server. The configuration option and default values are shown below:

    protocol_options: [
      max_header_name_length: 64,
      max_header_value_length: 4096,
      max_headers: 100,
      max_request_line_length: 8096
    ]

The error is a little confusing because the headers I have are:

[
  {"accept", "application/x-www-form-urlencoded"},
  {"content-type", "application/x-www-form-urlencoded"}
]

When I change the outbound request scheme to http, the tests pass and no error is raised

It appears that Bypass is not intended to make https outbound requests, and I'm wondering if that is the case, or if there is some sort of configuration I am missing?

Thank you in advance for your help!!!

debug_log should check the config at runtime (not compile time)

I'm having troubles making a minimal repro, but my basic problem is that:

  1. I have an app that depends on {:bypass, github: "PSPDFKit-labs/bypass", ref: "6ed6bee81e48653bcca31f03dec9506889fc44c6", only: :test}.
  2. I have a config/test.exs with config :bypass, enable_debug_log: false.
  3. I run mix test, no debug logs (this is expected).
  4. I change config/test.exs to config :bypass, enable_debug_log: true.
  5. Run mix test, still no debug logs (unexpected).
  6. Logs are not turned on until I completely reinstall bypass with MIX_ENV=test mix do deps.clean bypass, deps.get, deps.compile bypass.
  7. Similarly, switching true to false will take no effect until I recompile bypass.

I believe the underlying issue is that the debug_log macro is getting compiled with one environment setting, then bypass isn't getting recompiled in between different mix test runs. Ergo, the different config has no effect on the compiled bypass app.

Editing the deps/bypass contents locally, this fixes my issue (i.e., after making this change then recompiling with MIX_ENV=test mix deps.compile bypass, my config changes take immediate effect on subsequent mix test runs):

diff --git a/lib/bypass/utils.ex b/lib/bypass/utils.ex
index 915e7f5..24419a5 100644
--- a/lib/bypass/utils.ex
+++ b/lib/bypass/utils.ex
@@ -2,16 +2,12 @@ defmodule Bypass.Utils do
   @moduledoc false

   Application.load(:bypass)
-  if Application.get_env(:bypass, :enable_debug_log, false) do
-    defmacro debug_log(msg) do
-      quote bind_quoted: [msg: msg] do
+  defmacro debug_log(msg) do
+    quote bind_quoted: [msg: msg] do
+      if Application.get_env(:bypass, :enable_debug_log, false) do
         require Logger
         Logger.debug ["[bypass] ", msg]
       end
     end
-  else
-    defmacro debug_log(_msg) do
-      :ok
-    end
   end
 end

Again, sorry to not have a repro. Dunno what special alignment of moons I have in my actual project that's causing this issue, because I can't seem to trigger it with a "blank" app. But I think my reasoning is sound.

Thoughts?

After Upgrading bypass I am getting connection refused exceptions

I just upgraded bypass and cowboy and since then I cannot get my tests running. I created a minimal example like this:

Dependencies in mix.exs:

  defp deps do
    [
      {:bypass, "~> 1.0", only: :test},
      {:httpotion, "~> 3.1"},
      {:cowboy, "~> 2.6"},
    ]
  end

Test case:

defmodule BypassTestTest do

  use ExUnit.Case, async: true

  setup do
    bypass = Bypass.open |> IO.inspect
    {:ok, bypass: bypass}
  end

  test "client can handle an error response", %{bypass: bypass} do
    Bypass.expect bypass, fn conn ->
      assert conn.method == "GET"
      Plug.Conn.resp(conn, 200, ~s<{"msg": "Yay"}>)
    end
    IO.puts "REQUEST"

    url = endpoint_url(bypass.port) <> "/1.1/statuses/update.json"
    |> IO.inspect
    HTTPotion.get(url) |> IO.inspect
  end

  defp endpoint_url(port), do: "http://localhost:#{port}"
end

Console output:

Start ExUnit
%Bypass{pid: #PID<0.224.0>, port: 44067}
REQUEST
"http://localhost:44067/1.1/statuses/update.json"
%HTTPotion.ErrorResponse{message: "econnrefused"}


  1) test client can handle an error response (BypassTestTest)
     test/bypass_test_test.exs:10
     No HTTP request arrived at Bypass
     stacktrace:
       (bypass) lib/bypass.ex:86: Bypass.do_verify_expectations/2
       (ex_unit) lib/ex_unit/on_exit_handler.ex:140: ExUnit.OnExitHandler.exec_callback/1
       (ex_unit) lib/ex_unit/on_exit_handler.ex:126: ExUnit.OnExitHandler.on_exit_runner_loop/0



Finished in 0.06 seconds
1 test, 1 failure

The code is here: https://github.com/XOfSpades/bypass_test

Is this a bug or am I doing it somehow wrong? I mean I nearly copied your example from the README. Thank you for your help.

Support config to select network interface

Hi there,

I am trying to use bypass with docker-compose and so have requests coming to bypass from another docker container. It seems like bypass only listens on the loopback so can only be called from the same container it is running in.

It would be great if there was a config option to set the interface (with loopback as the default) to facilitate this kind of integration testing between docker containers.

Thanks for your work!

verify_expectations!/1 with ESpec doesn't work

I keep getting the following error when trying to use verify_expectations!/1 with ESpec.

** (FunctionClauseError) no function clause matching in Bypass.verify_expectations!/2
	      (bypass) lib/bypass.ex:59: Bypass.verify_expectations!(:espec, %Bypass{pid: #PID<0.762.0>, port: 5000})
	      (espec) lib/espec/example_runner.ex:151: anonymous fn/3 in ESpec.ExampleRunner.run_finallies/1
	      (espec) lib/espec/example_runner.ex:176: ESpec.ExampleRunner.call_with_rescue/2
	      (elixir) lib/enum.ex:1811: Enum."-reduce/3-lists^foldl/2-0-"/3
	      (espec) lib/espec/example_runner.ex:100: ESpec.ExampleRunner.after_example_actions/2
	      (espec) lib/espec/example_runner.ex:72: ESpec.ExampleRunner.try_run/3
	      (espec) lib/espec/example_runner.ex:39: ESpec.ExampleRunner.run_example/2
	      (espec) lib/espec/suite_runner.ex:76: ESpec.SuiteRunner.spawn_task/0

If I print out Code.ensure_loaded?(ESpec) I get true which means this should work.

My code:

before do
  bypass = Bypass.open(port: 5000)

  bypass |> bypass_source(params_for(:easypost_tracker))

  {:shared, bypass: bypass}
end

finally do
  Bypass.verify_expectations!(shared.bypass)
end

Can bypass handle concurrent requests?

I've created a test that I think shows bypass can't handle multiple requests (https://github.com/manukall/bypass/blob/concurrent_requests_test/test/bypass_test.exs#L118-L132).

It looks like the problem is in the already present retained_plug in the genservers state when the second request comes in. As there is an explicit pattern match on nil there, I assume this is a known limitation and intended behavior? How hard would it be to make bypass support concurrent requests?

Support config option to start without ExUnit hooks.

Hi there.

I'm using Bypass with espec. It'd be amazing if it were possible to just call Bypass.open without it exploding - I'm currently starting and stopping the bypass supervisor in my test setup and teardown.

Connection closure using Bypass with CircleCI and Heroku

Erlang v20
Elixir v1.4.5
Phoenix v1.3.0-rc.2
Bypass v0.7.0

We're getting intermittent build failures with CircleCI and Heroku. The following is the error that I cannot reproduce locally:

** (EXIT from #PID<0.796.0>) an exception was raised:
         ** (HTTPoison.Error) :closed
             (httpoison) lib/httpoison.ex:66: HTTPoison.request!/5
             (myapp) lib/myapp/api.ex:102: Client.API.request/6
             (myapp) lib/myapp/api.ex:90: Client.API.get!/3
             (myapp) lib/myapp/api.ex:58: Client.list_currencies!/1
             (con_cache) lib/con_cache/operations.ex:202: ConCache.Operations.dirty_get_or_store/3
             (con_cache) lib/con_cache/lock.ex:31: ConCache.Lock.exec/4
             (elixir) lib/task/supervised.ex:85: Task.Supervised.do_apply/2
             (elixir) lib/task/supervised.ex:36: Task.Supervised.reply/5
             (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
.18:12:38.524 [error] Task #PID<0.806.0> started from #PID<0.796.0> terminating

config/test.exs

# Bypass
config :bypass, port: 1234

HelperModule

bypass = Bypass.open(port: Application.get_env(:bypass, :port))
Bypass.expect bypass, "GET", "/v2/foo", fn conn ->
      Plug.Conn.resp(conn, 200, File.read!("#{File.cwd!}/test/bypass/foo.json"))
    end
end

sometest.exs

setup do
  bypass = Bypass.open(port: Application.get_env(:bypass, :port))
  {:ok, bypass: bypass}
end

Anybody encounter issues with their CI and/or with Heroku? Have any ideas about where to go from here?

Can Bypass be used for API calls that use Digest Auth?

I'm trying to use Bypass in my tests to return two different responses in quick succession. The only difference between the two requests is the Authentication headers; I may be wrong, but from reading the docs it doesn't look like Bypass differentiates responses based on this?

The reason for this is because the API I'm trying to test an API that relies on Digest Authentication, where an initial call to the endpoint returns a 401 Unauthorized with a nonce and realm. The second call involves using this nonce and realm, along with credentials to authenticate, after which a 200/201 is returned.

Is this something I can test with Bypass, or should I use something like Mox instead?

Has something fundamentally changed re Processes?

I have a test utility that lets you create multiple mock responses for a given URL

eg to mock a service erroring on the second call:

mock_responses(bypass, "GET", "/test_path", [
    %{status: 200, body: "SOME_BODY"},
    %{status: 500, body: "SOME_ERROR_BODY"}
])

it basically generated a function that used Process.put to store a counter of the calls and select the appropriate response from the list, this worked fine in Bypass 0.8, but in 1.0 it always finding the counter is not set (so replies with the first response).

So it seems previously each call to bypass ran in the same process, but does not anymore, is this a deliberate change, is it cowboy 2 related? What do you think would be an appropriate way around this? does bypass intend on offering or already offer similar built in functionality that I've missed?

0.9 release

Hey y'all. Thanks so much for this library. We use it very heavily. I was wondering if it would be possible to get a 0.9 release which would include the new stub function. If there's other stuff that needs to be finished up before that then I'm happy to help with those changes.

Thanks again for the work you put into this library.

Problem trying to access the endpoint

Hi! I'm doing

  test "capture from invoice.created", %{bypass: bypass} do
    Bypass.expect bypass, fn conn ->
      assert "PUT" == conn.method
      Plug.Conn.resp(conn, 201, ~s<{"response": "ok"}>)
    end

    send_invoice(bypass.port)
  end

def send_invoice(port) do
    case HTTPoison.put("http://localhost:#{port}/", Poison.encode!(to_send)) do
      {:ok, response} -> :ok
      {:error, reason} ->
        Logger.warn "Failed to callback the invoice. Reason: #{inspect reason}."
        :error
    end
  end

But it's failing with this error:

 No HTTP request arrived at Bypass
     stacktrace:
       (bypass) lib/bypass.ex:17: anonymous fn/1 in Bypass.open/0
       (ex_unit) lib/ex_unit/on_exit_handler.ex:82: ExUnit.OnExitHandler.exec_callback/1
       (ex_unit) lib/ex_unit/on_exit_handler.ex:66: ExUnit.OnExitHandler.on_exit_runner_loop/0

Am I doing something wrong?

Working example?

In the README, there's an example of how to use bypass to test a module, in the case TwitterClient.
The thing is, I'm not sure how TwitterClient is implemented, and should I go for implementing a similar module.
I understand that it involves using an http client.

I think a working example would be great for people trying to learn how to use bypass.
What do you think?

Intermittent :closed message caused by race condition

When running tests I am intermittently getting this error:

12:06:12.384 [error] Process #PID<0.1286.0> raised an exception
** (CaseClauseError) no case clause matching: <payload_here>
    (cowboy) /Users/elbow-jason/my_app/deps/cowboy/src/cowboy_protocol.erl:344: :cowboy_protocol.parse_hd_value/9
12:06:12.389 [error] Ranch protocol #PID<0.1286.0> (:cowboy_protocol) of listener #Reference<0.1060090579.823656453.233967> terminated
** (exit) an exception was raised:
    ** (CaseClauseError) no case clause matching: <payload_here>
        (cowboy) /Users/elbow-jason/my_app/deps/cowboy/src/cowboy_protocol.erl:344: :cowboy_protocol.parse_hd_value/9

with a failing test that says:

  1) test client can post! and handle the response (MyApp.NotifierClientTest)
     test/my_app/notifier_client_test.exs:12
     ** (HTTPoison.Error) :closed
     code: assert ^payload = NotifierClient.post!(endpoint_url(bypass.port), %{"hello" => "world"})
     stacktrace:
       (httpoison) lib/httpoison.ex:66: HTTPoison.request!/5
       (my_app) lib/my_app/notifier_client.ex:10: MyApp.NotifierClient.post!/2
       test/my_app/notifier_client_test.exs:18: (test)

here is the source:

  setup do
    bypass = Bypass.open
    {:ok, bypass: bypass}
  end

  test "client can post! and handle the response", %{bypass: bypass} do
    payload = %{"errors" => [%{"code" => 88, "message" => "Rate limit exceeded"}]}
    Bypass.expect_once bypass, "POST", "/", fn conn ->
      body = Poison.encode!(payload)
      Plug.Conn.resp(conn, 200, body)
    end
    assert ^payload = NotifierClient.post!(endpoint_url(bypass.port), %{"hello" => "world"})
  end

  defp endpoint_url(port), do: "http://localhost:#{port}/"

If I put a timer.sleep(100) in the setup block it stops happening.

Any suggestions/ideas for a solution?

failed assertions kill bypass process

When asserting in bypass, bypass dies and responds with a 500 no matter what

Server: localhost:55394 (http)
Request: GET /api/v2/accounts
** (exit) an exception was raised:
    ** (ExUnit.AssertionError)

Assertion with == failed
code:  assert Plug.Conn.get_req_header(conn, "content-type") == ["application/json"]
left:  []
right: ["application/json"]

(This is since upgrading to the latest elixir / phoenix)

It used to wait until the end of the test to check the asserts, is there now something special to do?

Cowboy plug has changed again

The release of plug 1.7.0 (which is required for Phoenix 1.4 rc.2) now uses plug_cowboy and there no longer is a Plug.Adapters.Cowboy2.

At minimum, the Bypass README needs updating, but I'm also seeing a number of deprecation warnings and some failed tests in our project related to Bypass (0.9.0).

New release?

Hi, thank you so much for bypass!

bypass master does not emit warnings on Elixir v1.10 (as well as Elixir master), so would it be possible to do a new release for those who want to be warning free? Thank you! โค๏ธ

Multiple expect_once

@ream88 any possibility of making it so that within one test I can stack two expect_once's so that the first call to the given route gets my first expect_once and the second call gets the second expect_once?
Currently the second expect_once would just redefine the first expect_once and I would get an error because the route is called twice.

Originally posted by @agramichael in #88 (comment)

Pipe expectations

Motivation

Would be nice to be able to pipe expectation calls, passing the bypass instance to next expectation.

bypass
|> Bypass.expect("POST", "/auth", fn conn ->
  # response ...
end)
|> Bypass.expect("POST", "/resource", fn conn ->
  # assert authenticated
  # response
end)

Similar to what mox does:

MyMock
|> expect(:add, fn x, y -> x + y end)
|> expect(:add, fn x, y -> x * y end)

Proposed solution

Return the bypass instance back in all expectation functions.

:simple_one_for_one strategy is deprecated

The :simple_one_for_one supervisor strategy was deprecated in Elixir 1.10. It's recommended to use DynamicSupervisor instead which was introduced in Elixir 1.6. As a result of this deprecation I've started to see the following warning whenever I run my test suite.

warning: :simple_one_for_one strategy is deprecated, please use DynamicSupervisor instead
  (elixir 1.10.1) lib/supervisor.ex:604: Supervisor.init/2
  (elixir 1.10.1) lib/supervisor.ex:556: Supervisor.start_link/2
  (kernel 6.5.1) application_master.erl:277: :application_master.start_it_old/4

I'd be happy to work on this if you'd accept a pull request.

QUESTION: Force header

I am trying to run tests against an API that returns JSON. I am setting up an individual test like so:

      Bypass.expect bypass, fn conn ->
        conn
        |> Plug.Conn.put_resp_header("content-type", "application/json")
        |> Plug.Conn.resp(200, json_response)
      end

Rather than have to add that put_resp_header/3 call in every test, is there a way to do this in my setup?

Bypass hangs in Bypass.do_verify_expectations/2 when using hackney inside a task with a timeout

I want to test that I'm able to abort a request that takes too long for any reason.
I decided to use a task to wrap my request, the task is shut down after a timeout.
I use bypass in my test to simulate a request that takes longer than the timeout value.

When using httpc to make the request the test succeeds.
When using hackney to make the request, the test hangs in Bypass.do_verify_expectations/2.

I use {:hackney, "~> 1.15"} and {:bypass, "~> 1.0.0", only: :test}.
Below is the test runner output and the code for the two tests.

1) test bypass with hackney (HackneyBypassTest)
     test/peertube_index/hackney_bypass_test.exs:5
     ** (ExUnit.TimeoutError) on_exit callback timed out after 2000ms. You can change the timeout:
     
       1. per test by setting "@tag timeout: x"
       2. per case by setting "@moduletag timeout: x"
       3. globally via "ExUnit.start(timeout: x)" configuration
       4. or set it to infinity per run by calling "mix test --trace"
          (useful when using IEx.pry)
     
     Timeouts are given as integers in milliseconds.
     
     stacktrace:
       (stdlib) gen.erl:169: :gen.do_call/4
       (elixir) lib/gen_server.ex:921: GenServer.call/3
       (bypass) lib/bypass.ex:71: Bypass.do_verify_expectations/2
       (ex_unit) lib/ex_unit/on_exit_handler.ex:140: ExUnit.OnExitHandler.exec_callback/1
       (ex_unit) lib/ex_unit/on_exit_handler.ex:126: ExUnit.OnExitHandler.on_exit_runner_loop/0
defmodule HackneyBypassTest do
  use ExUnit.Case

  @tag timeout: 2000
  test "bypass with hackney" do
    bypass = Bypass.open
    reponse_delay = 600
    timeout = reponse_delay - 100

    Bypass.expect(bypass, "GET", "/", fn conn ->
      Process.sleep(reponse_delay)
      Plug.Conn.resp(conn, 200, "response data")
    end)

    request = Task.async(fn ->
      :hackney.get("http://localhost:#{bypass.port}", [], "", [follow_redirect: true, with_body: true])
    end)
    result =
    case Task.yield(request, timeout) || Task.shutdown(request) do
      {:ok, result} ->
        {:ok, result}
      nil ->
        {:error, :timeout}
    end

    assert result == {:error, :timeout}
    IO.puts "DEBUG: End of test reached"
  end

  test "bypass with httpc" do
    bypass = Bypass.open
    reponse_delay = 600
    timeout = reponse_delay - 100

    Bypass.expect(bypass, "GET", "/", fn conn ->
      Process.sleep(reponse_delay)
      Plug.Conn.resp(conn, 200, "response data")
    end)

    request = Task.async(fn ->
      :httpc.request(:get, {String.to_charlist("http://localhost:#{bypass.port}"), []}, [], body_format: :binary)
    end)
    result =
    case Task.yield(request, timeout) || Task.shutdown(request) do
      {:ok, result} ->
        {:ok, result}
      nil ->
        {:error, :timeout}
    end

    assert result == {:error, :timeout}
    IO.puts "DEBUG: End of test reached"
  end
end

Help: Unable to match request

I've started two instances of Bypass one for each external service that a given controller action in Phoenix hits. Only one matches, the other returns:

%HTTPoison.Response{body: "",
 headers: [{"server", "Cowboy"}, {"date", "Tue, 22 Aug 2017 04:33:50 GMT"},
  {"content-length", "0"}], status_code: 500}

I've triple-checked the request method, host, and path, but I can't seem to get it to work. Am I missing something?

The service is set using an application env var which I set at run-time. I've attempted this in a test in IEx and set up a Bypass expect for the / (home) path to return a 200 response, but keep receiving the above. I'm out of luck here.

I call the external service's / path with a GET from a private function/plug inside my Phoenix controller, but I fail to see why that might be the issue.

Cowboy 2

Bypass is currently locked to cowboy ~> 1.0.

Are there plans to support cowboy 2.4.0?

race condition: can call `on_exit` handler before `put_expect_result`

It appears that the on_exit handler can be called and complete while there's still an outstanding Plug request. Here's a snapshot from the debug log:

2017-06-05 10:26:50.659 [debug] [bypass] #PID<0.364.0> retain_plug_process #PID<0.373.0>, retained_plug: nil
2017-06-05 10:26:50.660 [debug] [bypass] call(#PID<0.364.0>, :on_exit)
2017-06-05 10:26:50.660 [debug] [bypass] #PID<0.364.0> called :on_exit with state %{caller_awaiting_down: nil, expect_fun: #Function<3.125819828/1 in Test.test get_json/1 norma
l responses return a struct/1>, port: 63796, ref: #Reference<0.0.1.600>, request_result: {:error, :not_called}, retained_plug: #PID<0.373.0>, socket: #Port<0.8393>}
2017-06-05 10:26:50.660 [debug] [bypass] call(#PID<0.364.0>, {:put_expect_result, :ok_call})
2017-06-05 10:26:50.661 [debug] [bypass] #PID<0.364.0> -> {:error, :not_called}

The on_exit handler is called before the put_expect_result handler, so the test fails. My thought is that the on_exit handler should work similarly to the down handler, where it waits for a retained plug to complete before proceeding. I'll work on this, but wanted to run the approach by you all.

Testing in a Phoenix App?

I'm relatively new to Elixir, so apologies if I'm missing something obvious. I have an httpoison client as a module in a Phoenix application. The client makes calls to an external API. I'm replacing the external api endpoint in tests using bypass, but am struggling to find a clean approach to doing so. Currently I set the external api endpoint in a config file. The api client module reads the endpoint from application config for each external api call. Any tests that need to bypass the actual endpoint replace the endpoint in application config and reset it after the test completes. It works, but just feels wrong to me.

I've thought about changing the api client to a GenServer that takes the endpoint as a param to start_link (as in the bypass readme examples)...but in the context of Phoenix, the GenServer will already be started by the time the tests start...which brings me back to a config-based approach.

Anyway, I know this is more of a StackOverflow question, but since I don't see much activity there re: bypass, I thought I would post here first. If this not appropriate for the issues board, just lmk and I will remove and repost on SO. Thanks for any guidance...

Hex metadata

I have a local branch with the following change:

-      maintainers: ["pspdfkit.com"],
+      maintainers: ["PSPDFKit GmbH"],

I don't remember if I simply forgot to push that or if we decided against it. I would like to propose this change again, and we can add a link to pspdfkit.com in the Links section.

The future of bypass

Hey everybody,

Today @MSch and I had a nice talk about the future of bypass. And we have awesome news to share: I will join the bypass team and help shape its future from now on! Of course @MSch and the rest of the awesome @PSPDFKit team aren't going anywhere, but for now I'll be the primary contact person for all your bypass related things.

For the start we want to clean up the backlog by releasing a new major version (2.0?). I want to collect all issues and improvements which should definitely be in the next major version into the list below.

If you have any ideas and suggestions, feel free to comment here!

All the best, and a happy Monday! ๐Ÿ˜Š


Code and maintenance improvements:

  • Extend the test matrix to include more recent versions of Elixir: #89
  • Format code with mix format: #90
  • Format example code
  • Add links to source code from docs: #79

Breaking changes:

  • Switch from :simple_one_for_one to DynamicSupervisor: #87
  • Require at least Elixir 1.6
  • Replace gun with mint: #99

New features:

  • Allow the redefinition of routes: #80
  • Make listen interface configurable: #70
  • Add SO_REUSEPORT: #77
  • Add support for parametric routes: #93

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.