From a4299bdabc4fc11af642357b98091624df20cdba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Kociszewski?= Date: Tue, 22 Jul 2025 17:44:15 +0200 Subject: [PATCH 1/5] bugix: support min_calls and max_calls in expect_request! --- CHANGELOG.md | 6 ++++++ README.md | 2 +- lib/http_ex/backend/mock/expectation.ex | 5 ++++- mix.exs | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d91615..2145721 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v0.2.3 (2025-07-22) + +### Bug Fixes + +- Fixed `max_calls` and `min_calls` options not being respected in `expect_request!` + ## v0.2.2 (2025-04-30) ### Bug Fixes diff --git a/README.md b/README.md index 0dc8339..dd6b57e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ by adding `http_ex` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:http_ex, "~> 0.2.2"} + {:http_ex, "~> 0.2.3"} ] end ``` diff --git a/lib/http_ex/backend/mock/expectation.ex b/lib/http_ex/backend/mock/expectation.ex index b34cd33..1ff3e87 100644 --- a/lib/http_ex/backend/mock/expectation.ex +++ b/lib/http_ex/backend/mock/expectation.ex @@ -186,7 +186,10 @@ defmodule HTTPEx.Backend.Mock.Expectation do case type do :stub -> {0, :infinity} :reject -> {0, 0} - :assert -> {1, Keyword.get(opts, :calls, 1)} + :assert -> + min = Keyword.get(opts, :min_calls, 1) + max = Keyword.get(opts, :max_calls, Keyword.get(opts, :calls, 1)) + {min, max} end expectation_type = diff --git a/mix.exs b/mix.exs index e92c40b..bbc59a6 100644 --- a/mix.exs +++ b/mix.exs @@ -16,7 +16,7 @@ defmodule HttpEx.MixProject do source_url: "https://github.com/wuunder/http_ex", start_permanent: Mix.env() == :prod, test_coverage: [tool: ExCoveralls], - version: "0.2.2" + version: "0.2.3" ] end From 62c7118463b20e038e34154d73a42bb70ff534db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Kociszewski?= Date: Wed, 23 Jul 2025 09:54:25 +0200 Subject: [PATCH 2/5] add tests, remove calls option which was never used --- lib/http_ex/backend/mock/expectation.ex | 25 ++++- test/http_ex/backend/mock_test.exs | 119 ++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 4 deletions(-) diff --git a/lib/http_ex/backend/mock/expectation.ex b/lib/http_ex/backend/mock/expectation.ex index 1ff3e87..635e756 100644 --- a/lib/http_ex/backend/mock/expectation.ex +++ b/lib/http_ex/backend/mock/expectation.ex @@ -169,7 +169,8 @@ defmodule HTTPEx.Backend.Mock.Expectation do - expect_headers - expect_path - expect_query - - calls + - min_calls + - max_calls - stacktrace ## Examples @@ -184,11 +185,27 @@ defmodule HTTPEx.Backend.Mock.Expectation do {min_calls, max_calls} = case type do - :stub -> {0, :infinity} - :reject -> {0, 0} + :stub -> + {0, :infinity} + + :reject -> + {0, 0} + :assert -> min = Keyword.get(opts, :min_calls, 1) - max = Keyword.get(opts, :max_calls, Keyword.get(opts, :calls, 1)) + + max = + cond do + Keyword.has_key?(opts, :max_calls) -> + Keyword.get(opts, :max_calls) + + Keyword.has_key?(opts, :min_calls) -> + :infinity + + true -> + 1 + end + {min, max} end diff --git a/test/http_ex/backend/mock_test.exs b/test/http_ex/backend/mock_test.exs index c6eba80..e6802db 100644 --- a/test/http_ex/backend/mock_test.exs +++ b/test/http_ex/backend/mock_test.exs @@ -183,5 +183,124 @@ defmodule HTTPEx.Backend.MockTest do Mock.verify!(self()) end end + + test "min_calls not met" do + Mock.expect_request!( + endpoint: "http://www.example.com", + expect_body: "GET", + min_calls: 3, + response: %{status: 200, body: "OK"} + ) + + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get, + body: "GET" + }) + + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get, + body: "GET" + }) + + assert_raise AssertionError, + ~r/An expected HTTP call was called 2 but was expected to be called 3 times/, + fn -> + Mock.verify!(self()) + end + end + + test "min_calls met" do + Mock.expect_request!( + endpoint: "http://www.example.com", + expect_body: "GET", + min_calls: 2, + response: %{status: 200, body: "OK"} + ) + + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get, + body: "GET" + }) + + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get, + body: "GET" + }) + + Mock.verify!(self()) + end + + test "min_calls with max_calls met" do + Mock.expect_request!( + endpoint: "http://www.example.com", + expect_body: "GET", + min_calls: 1, + max_calls: 5, + response: %{status: 200, body: "OK"} + ) + + Enum.each(1..3, fn _ -> + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get, + body: "GET" + }) + end) + + Mock.verify!(self()) + end + + test "both min_calls met, max_calls met and then exceeded" do + Mock.expect_request!( + endpoint: "http://www.example.com", + expect_body: "GET", + min_calls: 2, + max_calls: 3, + response: %{status: 200, body: "OK"} + ) + + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get, + body: "GET" + }) + + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get, + body: "GET" + }) + + Mock.verify!(self()) + + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get, + body: "GET" + }) + + assert_raise AssertionError, + ~r/Maximum number of HTTP calls already made for request/, + fn -> + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get, + body: "GET" + }) + end + end end end From 8392d6fdde24e4f5ebbbf75dbda6efe57310462c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Kociszewski?= Date: Wed, 23 Jul 2025 11:28:26 +0200 Subject: [PATCH 3/5] refactor assertion and the tests --- lib/http_ex/backend/mock.ex | 1 + lib/http_ex/backend/mock/expectation.ex | 13 +-- test/http_ex/backend/mock_test.exs | 130 ++++++++++++++---------- 3 files changed, 81 insertions(+), 63 deletions(-) diff --git a/lib/http_ex/backend/mock.ex b/lib/http_ex/backend/mock.ex index caddeea..a436cb0 100644 --- a/lib/http_ex/backend/mock.ex +++ b/lib/http_ex/backend/mock.ex @@ -112,6 +112,7 @@ defmodule HTTPEx.Backend.Mock do @allwed_expect_request_options [ :body, :body_format, + :calls, :description, :expect_body, :expect_body_format, diff --git a/lib/http_ex/backend/mock/expectation.ex b/lib/http_ex/backend/mock/expectation.ex index 635e756..c1d696d 100644 --- a/lib/http_ex/backend/mock/expectation.ex +++ b/lib/http_ex/backend/mock/expectation.ex @@ -193,18 +193,7 @@ defmodule HTTPEx.Backend.Mock.Expectation do :assert -> min = Keyword.get(opts, :min_calls, 1) - - max = - cond do - Keyword.has_key?(opts, :max_calls) -> - Keyword.get(opts, :max_calls) - - Keyword.has_key?(opts, :min_calls) -> - :infinity - - true -> - 1 - end + max = Keyword.get(opts, :max_calls) || Keyword.get(opts, :calls, min) {min, max} end diff --git a/test/http_ex/backend/mock_test.exs b/test/http_ex/backend/mock_test.exs index e6802db..a914e9f 100644 --- a/test/http_ex/backend/mock_test.exs +++ b/test/http_ex/backend/mock_test.exs @@ -184,27 +184,20 @@ defmodule HTTPEx.Backend.MockTest do end end - test "min_calls not met" do + test "raise when less requests than min_calls expects are made" do Mock.expect_request!( endpoint: "http://www.example.com", - expect_body: "GET", min_calls: 3, response: %{status: 200, body: "OK"} ) - Mock.request(%Request{ - client: :httpoison, - url: "http://www.example.com", - method: :get, - body: "GET" - }) - - Mock.request(%Request{ - client: :httpoison, - url: "http://www.example.com", - method: :get, - body: "GET" - }) + Enum.each(1..2, fn _ -> + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get + }) + end) assert_raise AssertionError, ~r/An expected HTTP call was called 2 but was expected to be called 3 times/, @@ -216,24 +209,17 @@ defmodule HTTPEx.Backend.MockTest do test "min_calls met" do Mock.expect_request!( endpoint: "http://www.example.com", - expect_body: "GET", min_calls: 2, response: %{status: 200, body: "OK"} ) - Mock.request(%Request{ - client: :httpoison, - url: "http://www.example.com", - method: :get, - body: "GET" - }) - - Mock.request(%Request{ - client: :httpoison, - url: "http://www.example.com", - method: :get, - body: "GET" - }) + Enum.each(1..2, fn _ -> + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get + }) + end) Mock.verify!(self()) end @@ -241,7 +227,6 @@ defmodule HTTPEx.Backend.MockTest do test "min_calls with max_calls met" do Mock.expect_request!( endpoint: "http://www.example.com", - expect_body: "GET", min_calls: 1, max_calls: 5, response: %{status: 200, body: "OK"} @@ -251,44 +236,35 @@ defmodule HTTPEx.Backend.MockTest do Mock.request(%Request{ client: :httpoison, url: "http://www.example.com", - method: :get, - body: "GET" + method: :get }) end) Mock.verify!(self()) end - test "both min_calls met, max_calls met and then exceeded" do + test "raises when too many requests are made" do Mock.expect_request!( endpoint: "http://www.example.com", - expect_body: "GET", min_calls: 2, max_calls: 3, response: %{status: 200, body: "OK"} ) - Mock.request(%Request{ - client: :httpoison, - url: "http://www.example.com", - method: :get, - body: "GET" - }) - - Mock.request(%Request{ - client: :httpoison, - url: "http://www.example.com", - method: :get, - body: "GET" - }) + Enum.each(1..2, fn _ -> + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get + }) + end) Mock.verify!(self()) Mock.request(%Request{ client: :httpoison, url: "http://www.example.com", - method: :get, - body: "GET" + method: :get }) assert_raise AssertionError, @@ -297,8 +273,60 @@ defmodule HTTPEx.Backend.MockTest do Mock.request(%Request{ client: :httpoison, url: "http://www.example.com", - method: :get, - body: "GET" + method: :get + }) + end + end + + test "backward compatibility with calls option" do + Mock.expect_request!( + endpoint: "http://www.example.com", + calls: 2, + response: %{status: 200, body: "OK"} + ) + + Enum.each(1..2, fn _ -> + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get + }) + end) + + assert_raise AssertionError, + ~r/Maximum number of HTTP calls already made for request/, + fn -> + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get + }) + end + end + + test "max_calls takes precedence over calls option" do + Mock.expect_request!( + endpoint: "http://www.example.com", + calls: 5, + max_calls: 2, + response: %{status: 200, body: "OK"} + ) + + Enum.each(1..2, fn _ -> + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get + }) + end) + + assert_raise AssertionError, + ~r/Maximum number of HTTP calls already made for request/, + fn -> + Mock.request(%Request{ + client: :httpoison, + url: "http://www.example.com", + method: :get }) end end From 2d9894db8b22285aa69cda4836130310a900c6f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Kociszewski?= Date: Wed, 23 Jul 2025 12:19:55 +0200 Subject: [PATCH 4/5] refactor tests --- test/http_ex/backend/mock_test.exs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/test/http_ex/backend/mock_test.exs b/test/http_ex/backend/mock_test.exs index a914e9f..c973f0f 100644 --- a/test/http_ex/backend/mock_test.exs +++ b/test/http_ex/backend/mock_test.exs @@ -206,7 +206,7 @@ defmodule HTTPEx.Backend.MockTest do end end - test "min_calls met" do + test "does not raise an error when minimum amount of calls is reached" do Mock.expect_request!( endpoint: "http://www.example.com", min_calls: 2, @@ -251,7 +251,7 @@ defmodule HTTPEx.Backend.MockTest do response: %{status: 200, body: "OK"} ) - Enum.each(1..2, fn _ -> + Enum.each(1..3, fn _ -> Mock.request(%Request{ client: :httpoison, url: "http://www.example.com", @@ -259,14 +259,6 @@ defmodule HTTPEx.Backend.MockTest do }) end) - Mock.verify!(self()) - - Mock.request(%Request{ - client: :httpoison, - url: "http://www.example.com", - method: :get - }) - assert_raise AssertionError, ~r/Maximum number of HTTP calls already made for request/, fn -> From 5d35ac68ab997a9da3b5f74eca58a6cd4d2102e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Kociszewski?= Date: Wed, 23 Jul 2025 13:37:53 +0200 Subject: [PATCH 5/5] update test description --- test/http_ex/backend/mock_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/http_ex/backend/mock_test.exs b/test/http_ex/backend/mock_test.exs index c973f0f..28c4774 100644 --- a/test/http_ex/backend/mock_test.exs +++ b/test/http_ex/backend/mock_test.exs @@ -224,7 +224,7 @@ defmodule HTTPEx.Backend.MockTest do Mock.verify!(self()) end - test "min_calls with max_calls met" do + test "does not raise an error with the accepted amount of requests" do Mock.expect_request!( endpoint: "http://www.example.com", min_calls: 1,