Merge branch 'specs' into 'master'

Fix for arguments in mock stubs

Closes #44

See merge request arctic-fox/spectator!28
This commit is contained in:
Mike Miller 2020-03-28 17:44:05 +00:00
commit e585c36207
12 changed files with 434 additions and 23 deletions

View file

@ -361,10 +361,10 @@ Items not marked as completed may have partial implementations.
### How it Works (in a nutshell) ### How it Works (in a nutshell)
This shard makes extensive use of the Crystal macro system to build classes and modules. This shard makes extensive use of the Crystal macro system to build classes and modules.
Each `describe` and `context` block creates a new module nested in its parent. Each `describe` and `context` block creates a new class that inherits its parent.
The `it` block creates an example class. The `it` block creates an method.
An instance of the example class is created to run the test. An instance of the group class is created to run the test.
Each example class includes a context module, which contains all test values and hooks. Each group class includes all test values and hooks.
Contributing Contributing
------------ ------------
@ -379,11 +379,6 @@ Please make sure to run `crystal tool format` before submitting.
The CI build checks for properly formatted code. The CI build checks for properly formatted code.
[Ameba](https://crystal-ameba.github.io/) is run to check for code style. [Ameba](https://crystal-ameba.github.io/) is run to check for code style.
Tests must be written for any new functionality.
Macros that create types are not as easy to test,
so they are exempt for the current time.
However, please test all code locally with an example spec file.
Documentation is automatically generated and published to GitLab pages. Documentation is automatically generated and published to GitLab pages.
It can be found here: https://arctic-fox.gitlab.io/spectator It can be found here: https://arctic-fox.gitlab.io/spectator
@ -391,7 +386,17 @@ This project is developed on [GitLab](https://gitlab.com/arctic-fox/spectator),
and mirrored to [GitHub](https://github.com/icy-arctic-fox/spectator). and mirrored to [GitHub](https://github.com/icy-arctic-fox/spectator).
Issues and PRs/MRs are accepted on both. Issues and PRs/MRs are accepted on both.
Contributors ### Testing
------------
- [arctic-fox](https://gitlab.com/arctic-fox) Michael Miller - creator, maintainer Tests must be written for any new functionality.
The `spec/` directory contains feature tests as well as unit tests.
These demonstrate small bits of functionality.
The feature tests are grouped into sub directories based on their type, they are:
- docs/ - Example snippets from Spectator's documentation.
- rspec/ - Examples from RSpec's documentation modified slightly to work with Spectator.
See: https://relishapp.com/rspec/
Additional sub directories in this directory represent the modules/projects of RSpec.
The other directories are for unit testing various parts of Spectator.

View file

@ -1,5 +1,5 @@
name: spectator name: spectator
version: 0.9.11 version: 0.9.13
description: | description: |
A feature-rich spec testing framework for Crystal with similarities to RSpec. A feature-rich spec testing framework for Crystal with similarities to RSpec.

View file

@ -0,0 +1,23 @@
require "../spec_helper"
Spectator.describe String do
subject { "foo" }
describe "#==" do
context "with the same value" do
let(value) { subject.dup }
it "is true" do
is_expected.to eq(value)
end
end
context "with a different value" do
let(value) { "bar" }
it "is false" do
is_expected.to_not eq(value)
end
end
end
end

View file

@ -0,0 +1,57 @@
Spectator.describe String do
# This is a helper method.
def random_string(length)
chars = ('a'..'z').to_a
String.build(length) do |builder|
length.times { builder << chars.sample }
end
end
describe "#size" do
subject { random_string(10).size }
it "is the length of the string" do
is_expected.to eq(10)
end
end
end
Spectator.describe String do
# length is now pulled from value defined by `let`.
def random_string
chars = ('a'..'z').to_a
String.build(length) do |builder|
length.times { builder << chars.sample }
end
end
describe "#size" do
let(length) { 10 } # random_string uses this.
subject { random_string.size }
it "is the length of the string" do
is_expected.to eq(length)
end
end
end
module StringHelpers
def random_string
chars = ('a'..'z').to_a
String.build(length) do |builder|
length.times { builder << chars.sample }
end
end
end
Spectator.describe String do
include StringHelpers
describe "#size" do
let(length) { 10 }
subject { random_string.size }
it "is the length of the string" do
is_expected.to eq(length)
end
end
end

View file

@ -0,0 +1,30 @@
class Phonebook
def find(name)
# Some expensive lookup call.
"+18005554321"
end
end
class Resolver
def initialize(@phonebook : Phonebook)
end
def find(name)
@phonebook.find(name)
end
end
Spectator.describe Resolver do
mock Phonebook do
stub find(name)
end
describe "#find" do
it "can find number" do
pb = Phonebook.new
allow(pb).to receive(find).and_return("+18005551234")
resolver = Resolver.new(pb)
expect(resolver.find("Bob")).to eq("+18005551234")
end
end
end

View file

@ -0,0 +1,40 @@
require "../../spec_helper"
Spectator.describe "Doubles" do
double :my_double do
stub answer { 42 }
end
specify "the answer to everything" do
dbl = double(:my_double)
expect(dbl.answer).to eq(42)
end
end
class MyType
def answer
123
end
end
Spectator.describe "Mocks" do
mock MyType do
stub answer { 42 }
end
specify "the answer to everything" do
m = MyType.new
expect(m.answer).to eq(42)
end
end
Spectator.describe "Mocks and doubles" do
double :my_double do
stub answer : Int32 # Return type required, otherwise nil is assumed.
end
specify "the answer to everything" do
dbl = double(:my_double)
allow(dbl).to receive(:answer).and_return(42)
expect(dbl.answer).to eq(42)
end
end

View file

@ -0,0 +1,166 @@
require "../../spec_helper"
Spectator.describe "Stubs" do
context "Implementing a Stub" do
double :my_double do
stub answer : Int32
stub do_something
end
it "knows the answer" do
dbl = double(:my_double)
allow(dbl).to receive(:answer).and_return(42)
expect(dbl.answer).to eq(42)
end
it "does something" do
dbl = double(:my_double)
allow(dbl).to receive(:do_something)
expect(dbl.do_something).to be_nil
end
context "and_return" do
double :my_double do
stub to_s : String
stub do_something
end
it "stringifies" do
dbl = double(:my_double)
allow(dbl).to receive(:to_s).and_return("foobar")
expect(dbl.to_s).to eq("foobar")
end
it "returns gibberish" do
dbl = double(:my_double)
allow(dbl).to receive(:to_s).and_return("foo", "bar", "baz")
expect(dbl.to_s).to eq("foo")
expect(dbl.to_s).to eq("bar")
expect(dbl.to_s).to eq("baz")
expect(dbl.to_s).to eq("baz")
end
it "returns nil" do
dbl = double(:my_double)
allow(dbl).to receive(:do_something).and_return
expect(dbl.do_something).to be_nil
end
end
context "and_raise" do
double :my_double do
stub oops
end
it "raises an error" do
dbl = double(:my_double)
allow(dbl).to receive(:oops).and_raise(DivisionByZeroError.new)
expect { dbl.oops }.to raise_error(DivisionByZeroError)
end
it "raises an error" do
dbl = double(:my_double)
allow(dbl).to receive(:oops).and_raise("Something broke")
expect { dbl.oops }.to raise_error(/Something broke/)
end
it "raises an error" do
dbl = double(:my_double)
allow(dbl).to receive(:oops).and_raise(ArgumentError, "Size must be > 0")
expect { dbl.oops }.to raise_error(ArgumentError, /Size/)
end
end
context "and_call_original" do
class MyType
def foo
"foo"
end
end
mock MyType do
stub foo
end
it "calls the original" do
instance = MyType.new
allow(instance).to receive(:foo).and_call_original
expect(instance.foo).to eq("foo")
end
end
context "Short-hand for Multiple Stubs" do
double :my_double do
stub method_a : Symbol
stub method_b : Int32
stub method_c : String
end
it "does something" do
dbl = double(:my_double)
allow(dbl).to receive_messages(method_a: :foo, method_b: 42, method_c: "foobar")
expect(dbl.method_a).to eq(:foo)
expect(dbl.method_b).to eq(42)
expect(dbl.method_c).to eq("foobar")
end
end
context "Custom Implementation" do
double :my_double do
stub foo : String
end
it "does something" do
dbl = double(:my_double)
allow(dbl).to receive(:foo) { "foo" }
expect(dbl.foo).to eq("foo")
end
end
context "Arguments" do
double :my_double do
stub add(a, b) { a + b }
stub do_something(arg) { arg } # Return the argument by default.
end
it "adds two numbers" do
dbl = double(:my_double)
allow(dbl).to receive(:add).and_return(7)
expect(dbl.add(1, 2)).to eq(7)
end
it "does basic matching" do
dbl = double(:my_double)
allow(dbl).to receive(:do_something).with(1).and_return(42)
allow(dbl).to receive(:do_something).with(2).and_return(22)
expect(dbl.do_something(1)).to eq(42)
expect(dbl.do_something(2)).to eq(22)
end
it "can call the original" do
dbl = double(:my_double)
allow(dbl).to receive(:do_something).with(1).and_return(42)
allow(dbl).to receive(:do_something).with(2).and_call_original
expect(dbl.do_something(1)).to eq(42)
expect(dbl.do_something(2)).to eq(2)
end
it "falls back to the default" do
dbl = double(:my_double)
allow(dbl).to receive(:do_something).and_return(22)
allow(dbl).to receive(:do_something).with(1).and_return(42)
expect(dbl.do_something(1)).to eq(42)
expect(dbl.do_something(2)).to eq(22)
expect(dbl.do_something(3)).to eq(22)
end
it "does advanced matching" do
dbl = double(:my_double)
allow(dbl).to receive(:do_something).with(Int32).and_return(42)
allow(dbl).to receive(:do_something).with(String).and_return("foobar")
allow(dbl).to receive(:do_something).with(/hi/).and_return("hello there")
expect(dbl.do_something(1)).to eq(42)
expect(dbl.do_something("foo")).to eq("foobar")
expect(dbl.do_something("hi there")).to eq("hello there")
end
end
end
end

View file

@ -0,0 +1,34 @@
require "../spec_helper"
Spectator.describe String do
let(normal_string) { "foobar" }
let(empty_string) { "" }
describe "#empty?" do
subject { string.empty? }
context "when empty" do
let(string) { empty_string }
it "is true" do
is_expected.to be_true
end
end
context "when not empty" do
let(string) { normal_string }
it "is false" do
is_expected.to be_false
end
end
end
end
Spectator.describe Bytes do
it "stores an array of bytes" do
bytes = Bytes.new(32)
bytes[0] = 42
expect(bytes[0]).to eq(42)
end
end

32
spec/docs/subject_spec.cr Normal file
View file

@ -0,0 +1,32 @@
require "../spec_helper"
Spectator.describe "subject" do
subject(array1) { [1, 2, 3] }
subject(array2) { [4, 5, 6] }
it "has different elements" do
expect(array1).to_not eq(subject) # array2 would also work here.
end
let(string) { "foobar" }
it "isn't empty" do
expect(string.empty?).to be_false
end
it "is six characters" do
expect(string.size).to eq(6)
end
let(array) { [0, 1, 2] }
it "modifies the array" do
array[0] = 42
expect(array).to eq([42, 1, 2])
end
it "doesn't carry across tests" do
array[1] = 777
expect(array).to eq([0, 777, 2])
end
end

View file

@ -6,7 +6,7 @@ module Spectator
extend self extend self
# Current version of the Spectator library. # Current version of the Spectator library.
VERSION = "0.9.11" VERSION = "0.9.13"
# Top-level describe method. # Top-level describe method.
# All specs in a file must be wrapped in this call. # All specs in a file must be wrapped in this call.

View file

@ -50,12 +50,9 @@ module Spectator::Mocks
%} %}
{% if body && !body.is_a?(Nop) %} {% if body && !body.is_a?(Nop) %}
%source = ::Spectator::Source.new({{_file}}, {{_line}}) def {{receiver}}%method({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %}
%proc = ->{
{{body.body}} {{body.body}}
} end
%ds = ::Spectator::Mocks::ProcMethodStub.new({{name.symbolize}}, %source, %proc)
::Spectator::SpecBuilder.add_default_stub({{@type.name}}, %ds)
{% end %} {% end %}
def {{receiver}}{{name}}({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} def {{receiver}}{{name}}({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %}
@ -66,8 +63,15 @@ module Spectator::Mocks
if (%stub = %harness.mocks.find_stub(self, %call)) if (%stub = %harness.mocks.find_stub(self, %call))
return %stub.call!(%args) { {{original}}({{args.splat}}) } return %stub.call!(%args) { {{original}}({{args.splat}}) }
end end
{% if body && !body.is_a?(Nop) %}
%method({{args.splat}})
{% else %}
{{original}}({{args.splat}})
{% end %}
else
{{original}}({{args.splat}})
end end
{{original}}({{args.splat}})
end end
def {{receiver}}{{name}}({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} def {{receiver}}{{name}}({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %}
@ -78,9 +82,18 @@ module Spectator::Mocks
if (%stub = %harness.mocks.find_stub(self, %call)) if (%stub = %harness.mocks.find_stub(self, %call))
return %stub.call!(%args) { {{original}}({{args.splat}}) { |*%ya| yield *%ya } } return %stub.call!(%args) { {{original}}({{args.splat}}) { |*%ya| yield *%ya } }
end end
end
{{original}}({{args.splat}}) do |*%yield_args| {% if body && !body.is_a?(Nop) %}
yield *%yield_args %method({{args.splat}}) { {{original}}({{args.splat}}) { |*%ya| yield *%ya } }
{% else %}
{{original}}({{args.splat}}) do |*%yield_args|
yield *%yield_args
end
{% end %}
else
{{original}}({{args.splat}}) do |*%yield_args|
yield *%yield_args
end
end end
end end
end end

11
util/mirror-wiki.sh Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -ex
# Mirrors the contents of the GitLab wiki to GitHub.
git clone git@gitlab.com:arctic-fox/spectator.wiki.git
pushd spectator.wiki
git remote add github git@github.com:icy-arctic-fox/spectator.wiki.git
git fetch github
git push github master
popd
rm -rf spectator.wiki