Skip to content

Commit

Permalink
Add #have_error spec expectation (#1062)
Browse files Browse the repository at this point in the history
* Add `#have_error` spec expectation

* Add missing method return types

* Fix lint errors

As reported by *Ameba*:

```
src/avram/expectations/have_custom_error_expectation.cr:29:7 [Correctable]
[C] Style/RedundantReturn: Redundant `return` detected
> return <<-MSG
  ^

src/avram/expectations/have_error_expectation.cr:67:29
[W] Lint/ShadowingOuterLocalVar: Shadowing outer local variable `errors`
> errors.join do |name, errors|
```
  • Loading branch information
akadusei authored Aug 25, 2024
1 parent a604962 commit a10cb05
Show file tree
Hide file tree
Showing 5 changed files with 391 additions and 0 deletions.
112 changes: 112 additions & 0 deletions spec/avram/expectations/have_custom_error_expectation_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
require "../../spec_helper"

private class SaveEmailAddress < EmailAddress::SaveOperation
end

include Avram::Expectations

describe Avram::Expectations::HaveCustomErrorExpectation do
describe "#have_error" do
context "in positive assertions" do
it "passes if attribute is invalid" do
operation = SaveEmailAddress.new
operation.add_error(:providers, "is empty")

operation.should have_error
operation.should have_error("is empty")
operation.should have_error(/\sempty/)

operation.should have_error(:providers)
operation.should have_error(:providers, "is empty")
operation.should have_error(:providers, /\sempty/)
end

it "fails if attribute is valid" do
operation = SaveEmailAddress.new

expect_raises Spec::AssertionFailed, "have an error" do
operation.should have_error(:providers)
end

expect_raises Spec::AssertionFailed, "have the error " do
operation.should have_error(:providers, "is empty")
end

expect_raises Spec::AssertionFailed, "have the error " do
operation.should have_error(:providers, /\sempty/)
end
end

it "fails if attribute is invalid but without the given message" do
operation = SaveEmailAddress.new
operation.add_error(:providers, "is empty")

expect_raises Spec::AssertionFailed, "have the error " do
operation.should have_error("wrong message")
end

expect_raises Spec::AssertionFailed, "have the error " do
operation.should have_error(/\smessage/)
end

expect_raises Spec::AssertionFailed, "have the error " do
operation.should have_error(:providers, "wrong message")
end

expect_raises Spec::AssertionFailed, "have the error " do
operation.should have_error(:providers, /\smessage/)
end
end
end

context "in negative assertions" do
it "passes if attribute is valid" do
operation = SaveEmailAddress.new

operation.should_not have_error(:providers)
operation.should_not have_error(:providers, "is empty")
operation.should_not have_error(:providers, /\sempty/)
end

it "fails if attribute is invalid" do
operation = SaveEmailAddress.new
operation.add_error(:providers, "is empty")

expect_raises Spec::AssertionFailed, "not have an error" do
operation.should_not have_error
end

expect_raises Spec::AssertionFailed, "not have the error " do
operation.should_not have_error("is empty")
end

expect_raises Spec::AssertionFailed, "not have the error " do
operation.should_not have_error(/\sempty/)
end

expect_raises Spec::AssertionFailed, "not have an error" do
operation.should_not have_error(:providers)
end

expect_raises Spec::AssertionFailed, "not have the error " do
operation.should_not have_error(:providers, "is empty")
end

expect_raises Spec::AssertionFailed, "not have the error " do
operation.should_not have_error(:providers, /\sempty/)
end
end

it "passes if attribute is invalid but without the given message" do
operation = SaveEmailAddress.new
operation.add_error(:providers, "is required")

operation.should_not have_error("wrong message")
operation.should_not have_error(/\smessage/)

operation.should_not have_error(:providers, "wrong message")
operation.should_not have_error(:providers, /\smessage/)
end
end
end
end
128 changes: 128 additions & 0 deletions spec/avram/expectations/have_error_expectation_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
require "../../spec_helper"

private class SaveEmailAddress < EmailAddress::SaveOperation
end

include Avram::Expectations

describe Avram::Expectations::HaveErrorExpectation do
describe "#have_error" do
context "in positive assertions" do
it "passes if attribute is invalid" do
operation = SaveEmailAddress.new
operation.address.add_error("is required")

operation.should have_error
operation.should have_error("is required")
operation.should have_error(/\srequired/)

operation.address.should have_error
operation.address.should have_error("is required")
operation.address.should have_error(/\srequired/)
end

it "fails if attribute is valid" do
operation = SaveEmailAddress.new

expect_raises Spec::AssertionFailed, "have an error" do
operation.should have_error
end

expect_raises Spec::AssertionFailed, "have the error" do
operation.should have_error("is required")
end

expect_raises Spec::AssertionFailed, "have the error" do
operation.should have_error(/\srequired/)
end

expect_raises Spec::AssertionFailed, "have an error" do
operation.address.should have_error
end

expect_raises Spec::AssertionFailed, "have the error " do
operation.address.should have_error("is required")
end

expect_raises Spec::AssertionFailed, "have the error " do
operation.address.should have_error(/\srequired/)
end
end

it "fails if attribute is invalid but without the given message" do
operation = SaveEmailAddress.new
operation.address.add_error("is required")

expect_raises Spec::AssertionFailed, "have the error " do
operation.should have_error("wrong message")
end

expect_raises Spec::AssertionFailed, "have the error " do
operation.should have_error(/\smessage/)
end

expect_raises Spec::AssertionFailed, "have the error " do
operation.address.should have_error("wrong message")
end

expect_raises Spec::AssertionFailed, "have the error " do
operation.address.should have_error(/\smessage/)
end
end
end

context "in negative assertions" do
it "passes if attribute is valid" do
operation = SaveEmailAddress.new

operation.should_not have_error
operation.should_not have_error("is required")
operation.should_not have_error(/\srequired/)

operation.address.should_not have_error
operation.address.should_not have_error("is required")
operation.address.should_not have_error(/\srequired/)
end

it "fails if attribute is invalid" do
operation = SaveEmailAddress.new
operation.address.add_error("is required")

expect_raises Spec::AssertionFailed, "not have an error" do
operation.should_not have_error
end

expect_raises Spec::AssertionFailed, "not have the error " do
operation.should_not have_error("is required")
end

expect_raises Spec::AssertionFailed, "not have the error " do
operation.should_not have_error(/\srequired/)
end

expect_raises Spec::AssertionFailed, "not have an error" do
operation.address.should_not have_error
end

expect_raises Spec::AssertionFailed, "not have the error " do
operation.address.should_not have_error("is required")
end

expect_raises Spec::AssertionFailed, "not have the error " do
operation.address.should_not have_error(/\srequired/)
end
end

it "passes if attribute is invalid but without the given message" do
operation = SaveEmailAddress.new
operation.address.add_error("is required")

operation.should_not have_error("wrong message")
operation.should_not have_error(/\smessage/)

operation.address.should_not have_error("wrong message")
operation.address.should_not have_error(/\smessage/)
end
end
end
end
35 changes: 35 additions & 0 deletions src/avram/expectations.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module Avram::Expectations
# Tests that an operation or attribute has an error
#
# ```
# CreateReceipt.create(params) do |operation, receipt|
# receipt.should be_nil
#
# operation.should have_error
# operation.should have_error("is required")
# operation.should have_error(/\srequired/)
#
# operation.user_id.should have_error
# operation.user_id.should have_error("is required")
# operation.user_id.should have_error(/\srequired/)
# end
# ```
def have_error(message = nil)
HaveErrorExpectation.new(message)
end

# Tests that an operation has a custom error
#
# ```
# CreateUser.create(params) do |operation, user|
# user.should be_nil
#
# operation.should have_error(:roles)
# operation.should have_error(:roles, "is empty")
# operation.should have_error(:roles, /\sempty/)
# end
# ```
def have_error(name : Symbol, message = nil)
HaveCustomErrorExpectation.new(name, message)
end
end
35 changes: 35 additions & 0 deletions src/avram/expectations/have_custom_error_expectation.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module Avram::Expectations
struct HaveCustomErrorExpectation
def initialize(@name : Symbol, @message : Regex? = nil)
end

def self.new(name, message : String)
new(name, /#{message}/)
end

def match(operation : OperationErrors) : Bool
return false unless errors = operation.custom_errors[@name]?
@message.try { |message| return errors.any?(&.=~ message) }
!errors.empty?
end

def failure_message(operation : OperationErrors) : String
@message.try do |message|
return "Expected :#{@name} to have the error '#{message.source}'"
end

"Expected :#{@name} to have an error"
end

def negative_failure_message(operation : OperationErrors) : String
@message.try do |message|
return "Expected :#{@name} to not have the error '#{message.source}'"
end

<<-MSG
Expected :#{@name} to not have an error, got errors:
#{HaveErrorExpectation.list(operation.custom_errors[@name])}
MSG
end
end
end
81 changes: 81 additions & 0 deletions src/avram/expectations/have_error_expectation.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
module Avram::Expectations
struct HaveErrorExpectation
def initialize(@message : Regex? = nil)
end

def self.new(message : String)
new(/#{message}/)
end

def match(attribute : Attribute) : Bool
@message.try do |message|
return attribute.errors.any?(&.=~ message)
end

!attribute.errors.empty?
end

def match(operation : OperationErrors) : Bool
@message.try do |message|
return operation.errors.flat_map { |_, errors| errors }.any? do |error|
error =~ message
end
end

!operation.errors.empty?
end

def failure_message(attribute : Attribute) : String
@message.try do |message|
return "Expected :#{attribute.name} to have the error '#{message.source}'"
end

"Expected :#{attribute.name} to have an error"
end

def failure_message(operation : OperationErrors) : String
@message.try do |message|
return "Expected operation to have the error '#{message.source}'"
end

"Expected operation to have an error"
end

def negative_failure_message(attribute : Attribute) : String
@message.try do |message|
return "Expected :#{attribute.name} to not have the error '#{message.source}'"
end

<<-MSG
Expected :#{attribute.name} to not have an error, got errors:
#{self.class.list(attribute.errors)}
MSG
end

def negative_failure_message(operation : OperationErrors) : String
@message.try do |message|
return "Expected operation to not have the error '#{message.source}'"
end

<<-MSG
Expected operation to not have an error, got errors:
#{self.class.list(operation.errors)}
MSG
end

protected def self.list(errors : Hash)
errors.join do |name, _errors|
list _errors.map { |error| "#{name}: #{error}" }
end
end

protected def self.list(errors : Array)
errors.join do |error|
<<-ERROR
- #{error}
ERROR
end
end
end
end

0 comments on commit a10cb05

Please sign in to comment.