diff --git a/README.md b/README.md index f960ded..3f8420e 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ Calculator verify!() ``` -Using `expect/4` on intra-module functions will not work, unless the function is referenced by it's fully qualified name. +Using `expect/4` on intra-module functions will not work, unless the function is referenced by it's fully qualified name. ```elixir defmodule Calculator do @@ -206,7 +206,7 @@ To use DSL Mode `use Mimic.DSL` rather than `use Mimic` in your test. DSL Mode ``` ## Stubs with fake module -`stub_with/2` enable substitute function call of a module with another similar module +`stub_with/2` enable substitute function call of a module with another similar module. ```elixir defmodule BadCalculator do @@ -222,6 +222,18 @@ To use DSL Mode `use Mimic.DSL` rather than `use Mimic` in your test. DSL Mode end ``` +## Calling the original +`call_original/3` allows to call original unmocked version of the function. + +```elixir +setup :set_mimic_private + +test "calls original function even if it has been is stubbed" do + stub_with(Calculator, InverseCalculator) + + assert call_original(Calculator, :add, [1, 2]) == 3 +end +``` ## Implementation Details & Performance diff --git a/lib/mimic.ex b/lib/mimic.ex index 27d54d6..40a6936 100644 --- a/lib/mimic.ex +++ b/lib/mimic.ex @@ -374,6 +374,39 @@ defmodule Mimic do end end + @doc """ + Define a stub which must not be called. + + This function allows you do define a stub which must not be called during the + course of this test. If it is called then the verification step will raise. + + ## Arguments: + + * `module` - the name of the module in which we're adding the stub. + * `function_name` - the name of the function we're stubbing. + * `arity` - the arity of the function we're stubbing. + * `args` - the arguments of the function we're stubbing. + + ## Raises: + + * If `function` is not called by the stubbing process while calling `verify!/1`. + + ## Example: + + iex> Mimic.call_original(Calculator, :add, [1, 2]) + 3 + + """ + @spec call_original(module, atom, list) :: any + def call_original(module, function_name, args) do + arity = length(args) + + raise_if_not_exported_function!(module, function_name, arity) + func = Function.capture(Mimic.Module.original(module), function_name, arity) + + Kernel.apply(func, args) + end + @doc """ Verifies the current process after it exits. diff --git a/test/mimic_test.exs b/test/mimic_test.exs index 20eb6b3..fa48f8a 100644 --- a/test/mimic_test.exs +++ b/test/mimic_test.exs @@ -924,7 +924,7 @@ defmodule Mimic.Test do end end - describe "copy/1 with duplicates does nothing" do + describe "copy/1 with duplicates" do setup :set_mimic_private test "stubs still stub" do @@ -946,6 +946,44 @@ defmodule Mimic.Test do end end + describe "call_original/3" do + setup :set_mimic_private + + test "calls original function even if it has been is stubbed" do + stub_with(Calculator, InverseCalculator) + + assert call_original(Calculator, :add, [1, 2]) == 3 + end + + test "calls original function even if it has been rejected as a module function" do + Mimic.reject(Calculator, :add, 2) + + assert call_original(Calculator, :add, [1, 2]) == 3 + end + + test "calls original function even if it has been rejected as a capture" do + Mimic.reject(&Calculator.add/2) + + assert call_original(Calculator, :add, [1, 2]) == 3 + end + + test "when called on a function that has not been stubbed" do + assert call_original(Calculator, :add, [1, 2]) == 3 + end + + test "when called on a module that does not exist" do + assert_raise ArgumentError, "Function add/2 not defined for NonExistentModule", fn -> + call_original(NonExistentModule, :add, [1, 2]) + end + end + + test "when called on a function that does not exist" do + assert_raise ArgumentError, "Function non_existent_call/2 not defined for Calculator", fn -> + call_original(Calculator, :non_existent_call, [1, 2]) + end + end + end + describe "structs" do setup :set_mimic_private