docs/testing.md
# Testing
Clowne provides specific tools to help you test your cloners.
The main goal is to make it possible to test different cloning phases separately and avoid _heavy_ tests setup phases.
Let's consider the following models and cloners:
```ruby
# app/models/user.rb
class User < ApplicationRecord
has_one :profile
has_many :posts
end
# app/models/post.rb
class Post < ApplicationRecord
has_many :comments
has_many :votes
scope :draft, -> { where(draft: true) }
end
# app/cloners/user_cloner.rb
class UserCloner < Clowne::Cloner
class ProfileCloner
nullify :rating
end
include_association :profile, clone_with: ProfileCloner
nullify :email
finalize do |_, record, name: nil, **|
record.name = name unless name.nil?
end
trait :copy do
init_as do |user, target:, **|
# copy name
target.name = user.name
target
end
end
trait :with_posts do
include_association :posts, :draft, traits: :mark_as_copy
end
trait :with_popular_posts do
include_association :posts, (lambda do |params|
where("rating > ?", params[:min_rating])
end)
end
end
# app/cloners/post_cloner.rb
class PostCloner < Clowne::Cloner
include_association :comments
trait :mark_as_copy do |_, record|
record.title += " (copy)"
end
end
```
## Getting started
Currently, only [RSpec](http://rspec.info/) is supported.
Add this line to your `spec_helper.rb` (or `rails_helper.rb`):
```ruby
require "clowne/rspec"
```
## Configuration matchers
There are several matchers that allow you to verify the cloner configuration.
### `clone_associations`
This matcher vefifies that your cloner includes the specified associations:
```ruby
# spec/cloners/user_cloner_spec.rb
RSpec.describe UserCloner, type: :cloner do
subject { described_class }
specify do
# checks that only the specified associations is included
is_expected.to clone_associations(:profile)
# with traits
is_expected.to clone_associations(:profile, :posts)
.with_traits(:with_posts)
# raises when there are some unspecified associations
is_expected.to clone_associations(:profile)
.with_traits(:with_posts)
#=> raises RSpec::Expectations::ExpectationNotMetError
end
end
```
### `clone_association`
This matcher allows to verify the specified association options:
```ruby
# spec/cloners/user_cloner_spec.rb
RSpec.describe UserCloner, type: :cloner do
subject { described_class }
specify do
# simply check that association is included
is_expected.to clone_association(:profile)
# check options
is_expected.to clone_association(
:profile,
clone_with: described_class::ProfileCloner
)
# with traits, scope and activated trait
is_expected.to clone_association(
:posts,
traits: :mark_as_copy,
scope: :draft
).with_traits(:with_posts)
end
end
```
**NOTE:** `clone_associations`/`clone_association` matchers are only available in groups marked with `type: :cloner` tag.
Clowne automaticaly marks all specs in `spec/cloners` folder with `type: :cloner`. Otherwise you have to add this tag you.
## Using partial cloning
Under the hood, Clowne builds a [compilation plan](architecture.md) which is used to clone the record.
Plan is a set of _actions_ (such as `nullify`, `finalize`, `association`, `init_as`) which are applied to the record.
Most of the time these actions don't depend on each other, thus we can test them separately:
```ruby
# spec/cloners/user_cloner_spec.rb
RSpec.describe UserCloner, type: :cloner do
subject(:user) { create :user, name: "Bombon" }
specify "simple case" do
# apply only the specified part of the plan
cloned_user = described_class.partial_apply(:nullify, user).to_record
expect(cloned_user.email).to be_nil
# finalize wasn't applied
expect(cloned_user.name).to eq "Bombon"
end
specify "with params" do
cloned_user = described_class.partial_apply(:finalize, user, name: "new name").to_record
# nullify actions were not applied!
expect(cloned_user.email).to eq user.email
# finalize was applied
expect(cloned_user.name).to eq "new name"
end
specify "with traits" do
a_user = create(:user, name: "Dindon")
cloned_user = described_class.partial_apply(
:init_as, user, traits: :copy, target: a_user
).to_record
# returned user is the same as target
expect(cloned_user).to be_eql(a_user)
expect(cloned_user.name).to eq "Bombon"
end
specify "associations" do
create(:post, user: user, rating: 1, text: "Boom Boom")
create(:post, user: user, rating: 2, text: "Flying Dumplings")
# you can specify which associations to include (you can use array)
# to apply all associations write:
# plan.apply(:association)
cloned_user = described_class.partial_apply(
"association.posts", user, traits: :with_popular_posts, min_rating: 1
).to_record
expect(cloned_user.posts.size).to eq 1
expect(cloned_user.posts.first.text).to eq "Flying Dumplings"
end
end
```