diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f6b09d..a494c31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +### 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)). diff --git a/README.md b/README.md index f0206f2..caeb792 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,55 @@ IHateEvenNumbers.call # => #, @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/). diff --git a/lib/f_service/rspec.rb b/lib/f_service/rspec.rb new file mode 100644 index 0000000..eabc575 --- /dev/null +++ b/lib/f_service/rspec.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require_relative 'rspec/support' diff --git a/lib/f_service/rspec/support.rb b/lib/f_service/rspec/support.rb new file mode 100644 index 0000000..d83453d --- /dev/null +++ b/lib/f_service/rspec/support.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require_relative 'support/helpers' +require_relative 'support/matchers' diff --git a/lib/f_service/rspec/support/helpers.rb b/lib/f_service/rspec/support/helpers.rb new file mode 100644 index 0000000..6c90b16 --- /dev/null +++ b/lib/f_service/rspec/support/helpers.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require_relative 'helpers/result' diff --git a/lib/f_service/rspec/support/helpers/result.rb b/lib/f_service/rspec/support/helpers/result.rb new file mode 100644 index 0000000..6329174 --- /dev/null +++ b/lib/f_service/rspec/support/helpers/result.rb @@ -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 diff --git a/lib/f_service/rspec/support/matchers.rb b/lib/f_service/rspec/support/matchers.rb new file mode 100644 index 0000000..11bbdcf --- /dev/null +++ b/lib/f_service/rspec/support/matchers.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require_relative 'matchers/result' diff --git a/lib/f_service/rspec/support/matchers/result.rb b/lib/f_service/rspec/support/matchers/result.rb new file mode 100644 index 0000000..0e7b900 --- /dev/null +++ b/lib/f_service/rspec/support/matchers/result.rb @@ -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 diff --git a/spec/f_service/base_spec.rb b/spec/f_service/base_spec.rb index 2f24155..b0fa1a0 100644 --- a/spec/f_service/base_spec.rb +++ b/spec/f_service/base_spec.rb @@ -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 } } diff --git a/spec/f_service/result/failure_spec.rb b/spec/f_service/result/failure_spec.rb index fd32310..6aa72ad 100644 --- a/spec/f_service/result/failure_spec.rb +++ b/spec/f_service/result/failure_spec.rb @@ -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 @@ -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 diff --git a/spec/f_service/result/success_spec.rb b/spec/f_service/result/success_spec.rb index 02e6a50..5493da9 100644 --- a/spec/f_service/result/success_spec.rb +++ b/spec/f_service/result/success_spec.rb @@ -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 @@ -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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 00470a4..ee551ae 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -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