• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

parroty / exvcr / fd003373fe50f46067004d3f915027956ca9447d-PR-203

pending completion
fd003373fe50f46067004d3f915027956ca9447d-PR-203

Pull #203

github

parroty
Pull Request #203: Add description for start / stop

577 of 642 relevant lines covered (89.88%)

95.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.0
/lib/exvcr/mock.ex
1
defmodule ExVCR.Mock do
2
  @moduledoc """
3
  Provides macro to record HTTP request/response.
4
  """
5

6
  alias ExVCR.Recorder
7

8
  defmacro __using__(opts) do
9
    adapter = opts[:adapter] || ExVCR.Adapter.IBrowse
17✔
10
    options = opts[:options]
17✔
11

12
    quote do
13
      import ExVCR.Mock
14
      :application.start(unquote(adapter).module_name)
15
      use unquote(adapter)
16

17
      def adapter_method() do
18
        unquote(adapter)
19
      end
20

21
      def options_method() do
22
        unquote(options)
23
      end
24
    end
25
  end
26

27
  @doc """
28
  Provides macro to trigger recording/replaying http interactions.
29

30
  ## Options
31

32
  - `:match_requests_on` A list of request properties to match on when
33
    finding a matching response. Valid values include `:query`, `:headers`,
34
    and `:request_body`
35

36
  """
37
  defmacro use_cassette(:stub, options, test) do
38
    quote do
39
      stub_fixture = "stub_fixture_#{ExVCR.Util.uniq_id}"
40
      stub = prepare_stub_record(unquote(options), adapter_method())
41
      recorder = Recorder.start([fixture: stub_fixture, stub: stub, adapter: adapter_method()])
42

43
      try do
44
        mock_methods(recorder, adapter_method())
45
        [do: return_value] = unquote(test)
46
        return_value
47
      after
48
        module_name = adapter_method().module_name
49
        unload(module_name)
50
        ExVCR.MockLock.release_lock()
51
      end
52
    end
53
  end
54

55
  defmacro use_cassette(fixture, options, test) do
56
    quote do
57
      recorder = start_cassette(unquote(fixture), unquote(options))
58

59
      try do
60
        [do: return_value] = unquote(test)
61
        return_value
62
      after
63
        stop_cassette(recorder)
64
      end
65
    end
66
  end
67

68
  defmacro use_cassette(fixture, test) do
69
    quote do
70
      use_cassette(unquote(fixture), [], unquote(test))
71
    end
72
  end
73

74
  defmacro start_cassette(fixture, options) when fixture != :stub do
75
    quote do
76
      recorder =
77
        Recorder.start(
78
          unquote(options) ++
79
            [fixture: normalize_fixture(unquote(fixture)), adapter: adapter_method()]
80
        )
81

82
      mock_methods(recorder, adapter_method())
83
      recorder
84
    end
85
  end
86

87
  defmacro stop_cassette(recorder) do
88
    quote do
89
      recorder_result = Recorder.save(unquote(recorder))
90

91
      module_name = adapter_method().module_name
92
      unload(module_name)
93
      ExVCR.MockLock.release_lock()
94

95
      recorder_result
96
    end
97
  end
98

99
  @doc false
100
  defp load(adapter, recorder) do
101
    if ExVCR.Application.global_mock_enabled?() do
158✔
102
      ExVCR.Actor.CurrentRecorder.set(recorder)
158✔
103
    else
104
      module_name    = adapter.module_name
×
105
      target_methods = adapter.target_methods(recorder)
×
106
      Enum.each(target_methods, fn({function, callback}) ->
×
107
        :meck.expect(module_name, function, callback)
×
108
      end)
109
    end
110
  end
111

112
  @doc false
113
  def unload(module_name) do
114
    if ExVCR.Application.global_mock_enabled?() do
157✔
115
      ExVCR.Actor.CurrentRecorder.default_state()
116
      |> ExVCR.Actor.CurrentRecorder.set()
157✔
117
    else
118
      :meck.unload(module_name)
×
119
    end
120
  end
121

122
  @doc """
123
  Mock methods pre-defined for the specified adapter.
124
  """
125
  def mock_methods(recorder, adapter) do
126
    parent_pid = self()
158✔
127
    Task.async(fn ->
128
      ExVCR.MockLock.ensure_started
158✔
129
      ExVCR.MockLock.request_lock(self(), parent_pid)
158✔
130
      receive do
158✔
131
        :lock_granted ->
132
          load(adapter, recorder)
158✔
133
      end
134
    end)
135
    |> Task.await(:infinity)
158✔
136
  end
137

138
  @doc """
139
  Prepare stub record based on specified option parameters.
140
  """
141
  def prepare_stub_record(options, adapter) do
142
    method        = (options[:method] || "get") |> to_string
17✔
143
    url           = (options[:url] || "~r/.+/") |> to_string
17✔
144
    body          = (options[:body] || "Hello World") |> to_string
17✔
145
    # REVIEW: would be great to have "~r/.+/" as default request_body
146
    request_body  = (options[:request_body] || "") |> to_string
17✔
147

148
    headers     = options[:headers] || adapter.default_stub_params(:headers)
17✔
149
    status_code = options[:status_code] || adapter.default_stub_params(:status_code)
17✔
150

151
    record = %{ "request"  => %{"method" => method, "url" => url, "request_body" => request_body},
17✔
152
                "response" => %{"body" => body, "headers"  => headers, "status_code" => status_code} }
153

154
    [adapter.convert_from_string(record)]
155
  end
156

157
  @doc """
158
  Normalize fixture name for using as json file names, which removes whitespaces and align case.
159
  """
160
  def normalize_fixture(fixture) do
161
    fixture |> String.replace(~r/\s/, "_") |> String.downcase
141✔
162
  end
163
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc