Skip to content

Commit

Permalink
Merge pull request #35 from Fretadao/bv-add-spec-support
Browse files Browse the repository at this point in the history
Add RSpec support for mock and match results
  • Loading branch information
j133y committed May 6, 2022
2 parents 3abdc25 + e4be6a6 commit ee404dd
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 26 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
<!-- ### Removed -->
---

### Added

- Rspec Helper mock_service;
- Rspec Matcher have_succeed_with and have_failed_with

## 0.2.0
### Added
- Add `and_then` as alias for `then` (partially fix [#23](https://github.com/Fretadao/f_service/issues/23)).
Expand Down
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,55 @@ IHateEvenNumbers.call
# => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @type=:rand_int>
```

## Testing

We provide some helpers and matchers to make ease to test code envolving Fservice services.

To make available in the system, in the file 'spec/spec_helper.rb' or 'spec/rails_helper.rb'

add the folowing require:

```rb
require 'f_service/rspec'
```

### Mocking a result

```
mock_service(Uer::Create)
# => Mocks a successful result with all values nil
mock_service(Uer::Create, result: :success)
# => Mocks a successful result with all values nil
mock_service(Uer::Create, result: :success, type: :created)
# => Mocks a successful result with type created
mock_service(Uer::Create, result: :success, type: :created, value: instance_spy(User))
# => Mocks a successful result with type created and a value
mock_service(Uer::Create, result: :failure)
# => Mocs a failure with all nil values
mock_service(Uer::Create, result: :failure, type: :invalid_attributes)
# => Mocs a failure with a failure type
mock_service(Uer::Create, result: :failure, type: :invalid_attributes, value: { name: ["can't be blank"] })
# => Mocs a failure with a failure type and an error value
```

### Matching a result

```rb
expect(User::Create.(name: 'Joe')).to have_succeed_with(:created)

expect(User::Create.(name: 'Joe')).to have_succeed_with(:created).and_value(an_instance_of(User))

expect(User::Create.(name: nil)).to have_failed_with(:invalid_attributes)

expect(User::Create.(name: nil)).to have_failed_with(:invalid_attributes).and_error({ name: ["can't be blank"] })
```

## API Docs

You can access the API docs [here](https://www.rubydoc.info/gems/f_service/).
Expand Down
3 changes: 3 additions & 0 deletions lib/f_service/rspec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# frozen_string_literal: true

require_relative 'rspec/support'
4 changes: 4 additions & 0 deletions lib/f_service/rspec/support.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

require_relative 'support/helpers'
require_relative 'support/matchers'
3 changes: 3 additions & 0 deletions lib/f_service/rspec/support/helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# frozen_string_literal: true

require_relative 'helpers/result'
23 changes: 23 additions & 0 deletions lib/f_service/rspec/support/helpers/result.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

# Methods to mock a FService result from a service call.
module FServiceResultHelpers
# Create an Fservice result Success or Failure.
def f_service_result(result, value = nil, type = nil)
if result == :success
FService::Result::Success.new(value, type)
else
FService::Result::Failure.new(value, type)
end
end

# Mock a Fservice service call returning a result.
def mock_service(service, result: :success, value: nil, type: nil)
service_result = f_service_result(result, value, type)
allow(service).to receive(:call).and_return(service_result)
end
end

RSpec.configure do |config|
config.include FServiceResultHelpers
end
3 changes: 3 additions & 0 deletions lib/f_service/rspec/support/matchers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# frozen_string_literal: true

require_relative 'matchers/result'
61 changes: 61 additions & 0 deletions lib/f_service/rspec/support/matchers/result.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# frozen_string_literal: true

require 'rspec/expectations'

RSpec::Matchers.define :have_failed_with do |expected|
match do |actual|
matched = actual.is_a?(FService::Result::Failure) && actual.type == expected

matched &&= actual.error == @expected_error if defined?(@expected_error)

matched
end

chain :and_error do |expected_error|
@expected_error = expected_error
end

failure_message do |actual|
if actual.is_a?(FService::Result::Failure)
message = "expected failure's type '#{actual.type.inspect}' to be equal '#{expected.inspect}'"
if defined?(@expected_error)
has_description = @expected_error.respond_to?(:description)
message += " and error '#{actual.error.inspect}' to be "
message += has_description ? @expected_error.description : "equal '#{@expected_error.inspect}'"
end

message
else
"result '#{actual.inspect}' is not a Failure object"
end
end
end

RSpec::Matchers.define :have_succeed_with do |expected|
match do |actual|
matched = actual.is_a?(FService::Result::Success) && actual.type == expected

matched &&= values_match?(@expected_value, actual.value) if defined?(@expected_value)

matched
end

chain :and_value do |expected_value|
@expected_value = expected_value
end

failure_message do |actual|
if actual.is_a?(FService::Result::Success)
message = "expected success's type '#{actual.type.inspect}' to be equal '#{expected.inspect}'"
if defined?(@expected_value)
has_description = @expected_value.respond_to?(:description)
message += " and value '#{actual.value.inspect}' to be "
message += has_description ? @expected_value.description : "equal '#{@expected_value.inspect}'"
end

message
else
"result '#{actual.inspect}' is not a Success object"
end
end
end
10 changes: 6 additions & 4 deletions spec/f_service/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@
end

describe '#Check' do
subject(:response) { described_class.new.Check(:math_works) { 1 < 2 } }
context 'when block evaluates to true' do
subject(:response) { described_class.new.Check(:math_works) { 1 < 2 } }

it { expect(response).to be_successful }
it { expect(response.type).to eq(:math_works) }
it { expect(response.value!).to eq(true) }
it { expect(response).to be_successful }
it { expect(response.type).to eq(:math_works) }
it { expect(response.value!).to eq(true) }
end

context 'when block evaluates to false' do
subject(:response) { described_class.new.Check(:math_works) { 1 > 2 } }
Expand Down
26 changes: 15 additions & 11 deletions spec/f_service/result/failure_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@
require 'spec_helper'

RSpec.describe FService::Result::Failure do
subject(:failure) { described_class.new('Whoops!') }
describe 'initialize' do
subject(:failure) { described_class.new('Whoops!') }

it { expect(failure).to be_a described_class }
it { expect(failure).to be_failed }
it { expect(failure).not_to be_successful }
it { expect(failure.error).to eq('Whoops!') }
it { expect(failure.type).to eq(nil) }
it { expect(failure.value).to eq(nil) }
it { expect { failure.value! }.to raise_error FService::Result::Error }
it { expect(failure).to be_a described_class }
it { expect(failure).to be_failed }
it { expect(failure).not_to be_successful }
it { expect(failure.error).to eq('Whoops!') }
it { expect(failure.type).to eq(nil) }
it { expect(failure.value).to eq(nil) }
it { expect { failure.value! }.to raise_error FService::Result::Error }

context 'when defining a type' do
subject(:failure) { described_class.new('Whoops!', :error) }
context 'when defining a type' do
subject(:failure) { described_class.new('Whoops!', :error) }

it { expect(failure.type).to eq(:error) }
it { expect(failure.type).to eq(:error) }
end
end

describe '#on_failure' do
Expand Down Expand Up @@ -133,6 +135,8 @@
end

describe '#to_s' do
subject(:failure) { described_class.new('Whoops!') }

it { expect(failure.to_s).to eq 'Failure("Whoops!")' }
end
end
26 changes: 15 additions & 11 deletions spec/f_service/result/success_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@
require 'spec_helper'

RSpec.describe FService::Result::Success do
subject(:success) { described_class.new('Yay!') }
describe 'initialize' do
subject(:success) { described_class.new('Yay!') }

it { expect(success).to be_a described_class }
it { expect(success).to be_successful }
it { expect(success).not_to be_failed }
it { expect(success.value).to eq('Yay!') }
it { expect(success.type).to eq(nil) }
it { expect(success.error).to eq(nil) }
it { expect(success.value!).to eq('Yay!') }
it { expect(success).to be_a described_class }
it { expect(success).to be_successful }
it { expect(success).not_to be_failed }
it { expect(success.value).to eq('Yay!') }
it { expect(success.type).to eq(nil) }
it { expect(success.error).to eq(nil) }
it { expect(success.value!).to eq('Yay!') }

context 'when defining a type' do
subject(:success) { described_class.new('Yay!', :ok) }
context 'when defining a type' do
subject(:success) { described_class.new('Yay!', :ok) }

it { expect(success.type).to eq(:ok) }
it { expect(success.type).to eq(:ok) }
end
end

describe '#on_success' do
Expand Down Expand Up @@ -132,6 +134,8 @@
end

describe '#to_s' do
subject(:success) { described_class.new('Yay!') }

it { expect(success.to_s).to eq 'Success("Yay!")' }
end
end
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

require 'bundler/setup'
require 'f_service'
require 'f_service/rspec'

RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure
Expand Down

0 comments on commit ee404dd

Please sign in to comment.