Add support for defining explicit dependencies to QA factories

This commit is contained in:
Grzegorz Bizon 2017-12-21 09:38:06 +00:00 committed by Rémy Coutable
parent 88f41bf61f
commit 6df36c9d90
13 changed files with 327 additions and 50 deletions

View file

@ -17,6 +17,8 @@ module QA
#
module Factory
autoload :Base, 'qa/factory/base'
autoload :Dependency, 'qa/factory/dependency'
autoload :Product, 'qa/factory/product'
module Resource
autoload :Sandbox, 'qa/factory/resource/sandbox'

View file

@ -1,15 +1,34 @@
module QA
module Factory
class Base
def fabricate!(*_args)
raise NotImplementedError
end
def self.fabricate!(*args)
new.tap do |factory|
Factory::Product.populate!(new) do |factory|
yield factory if block_given?
return factory.fabricate!(*args)
dependencies.each do |name, signature|
Factory::Dependency.new(name, factory, signature).build!
end
factory.fabricate!(*args)
end
end
def fabricate!(*_args)
raise NotImplementedError
def self.dependencies
@dependencies ||= {}
end
def self.dependency(factory, as:, &block)
as.tap do |name|
class_eval { attr_accessor name }
Dependency::Signature.new(factory, block).tap do |signature|
dependencies.store(name, signature)
end
end
end
end
end

View file

@ -0,0 +1,38 @@
module QA
module Factory
class Dependency
Signature = Struct.new(:factory, :block)
def initialize(name, factory, signature)
@name = name
@factory = factory
@signature = signature
end
def overridden?
!!@factory.public_send(@name)
end
def build!
return if overridden?
Builder.new(@signature).fabricate!.tap do |product|
@factory.public_send("#{@name}=", product)
end
end
class Builder
def initialize(signature)
@factory = signature.factory
@block = signature.block
end
def fabricate!
@factory.fabricate! do |factory|
@block&.call(factory)
end
end
end
end
end
end

26
qa/qa/factory/product.rb Normal file
View file

@ -0,0 +1,26 @@
require 'capybara/dsl'
module QA
module Factory
class Product
include Capybara::DSL
def initialize(factory)
@factory = factory
@location = current_url
end
def visit!
visit @location
end
def self.populate!(factory)
raise ArgumentError unless block_given?
yield factory
new(factory)
end
end
end
end

View file

@ -1,16 +1,13 @@
require "pry-byebug"
module QA
module Factory
module Repository
class Push < Factory::Base
PAGE_REGEX_CHECK =
%r{\/#{Runtime::Namespace.sandbox_name}\/qa-test[^\/]+\/{1}[^\/]+\z}.freeze
attr_writer :file_name, :file_content, :commit_message, :branch_name
attr_writer :file_name,
:file_content,
:commit_message,
:branch_name
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-with-code'
project.description = 'Project with repository'
end
def initialize
@file_name = 'file.txt'
@ -20,12 +17,10 @@ module QA
end
def fabricate!
project.visit!
Git::Repository.perform do |repository|
repository.location = Page::Project::Show.act do
unless PAGE_REGEX_CHECK.match(current_path)
raise "To perform this scenario the current page should be project show."
end
choose_repository_clone_http
repository_location
end

View file

@ -4,12 +4,22 @@ module QA
class Group < Factory::Base
attr_writer :path, :description
dependency Factory::Resource::Sandbox, as: :sandbox
def initialize
@path = Runtime::Namespace.name
@description = "QA test run at #{Runtime::Namespace.time}"
end
def fabricate!
sandbox.visit!
Page::Group::Show.perform do |page|
if page.has_subgroup?(@path)
page.go_to_subgroup(@path)
else
page.go_to_new_subgroup
Page::Group::New.perform do |group|
group.set_path(@path)
group.set_description(@description)
@ -20,4 +30,6 @@ module QA
end
end
end
end
end
end

View file

@ -6,26 +6,17 @@ module QA
class Project < Factory::Base
attr_writer :description
dependency Factory::Resource::Group, as: :group
def name=(name)
@name = "#{name}-#{SecureRandom.hex(8)}"
@description = 'My awesome project'
end
def fabricate!
Factory::Resource::Sandbox.fabricate!
group.visit!
Page::Group::Show.perform do |page|
if page.has_subgroup?(Runtime::Namespace.name)
page.go_to_subgroup(Runtime::Namespace.name)
else
page.go_to_new_subgroup
Factory::Resource::Group.fabricate! do |group|
group.path = Runtime::Namespace.name
end
end
page.go_to_new_project
end
Page::Group::Show.act { go_to_new_project }
Page::Project::New.perform do |page|
page.choose_test_namespace

View file

@ -6,18 +6,24 @@ module QA
# creating it if it doesn't yet exist.
#
class Sandbox < Factory::Base
def initialize
@name = Runtime::Namespace.sandbox_name
end
def fabricate!
Page::Main::Menu.act { go_to_groups }
Page::Dashboard::Groups.perform do |page|
if page.has_group?(Runtime::Namespace.sandbox_name)
page.go_to_group(Runtime::Namespace.sandbox_name)
if page.has_group?(@name)
page.go_to_group(@name)
else
page.go_to_new_group
Resource::Group.fabricate! do |group|
group.path = Runtime::Namespace.sandbox_name
group.description = 'GitLab QA Sandbox'
Page::Group::New.perform do |group|
group.set_path(@name)
group.set_description('GitLab QA Sandbox')
group.set_visibility('Private')
group.create
end
end
end

View file

@ -21,6 +21,7 @@ module QA
find('.dropdown-toggle').click
find("li[data-value='new-subgroup']").click
end
find("input[data-action='new-subgroup']").click
end
@ -29,6 +30,7 @@ module QA
find('.dropdown-toggle').click
find("li[data-value='new-project']").click
end
find("input[data-action='new-project']").click
end
end

View file

@ -5,15 +5,10 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::Project.fabricate! do |scenario|
scenario.name = 'project_with_code'
scenario.description = 'project with repository'
end
Factory::Repository::Push.fabricate! do |scenario|
scenario.file_name = 'README.md'
scenario.file_content = '# This is test project'
scenario.commit_message = 'Add README.md'
Factory::Repository::Push.fabricate! do |push|
push.file_name = 'README.md'
push.file_content = '# This is a test project'
push.commit_message = 'Add README.md'
end
Page::Project::Show.act do
@ -22,7 +17,7 @@ module QA
end
expect(page).to have_content('README.md')
expect(page).to have_content('This is test project')
expect(page).to have_content('This is a test project')
end
end
end

View file

@ -0,0 +1,88 @@
describe QA::Factory::Base do
describe '.fabricate!' do
subject { Class.new(described_class) }
let(:factory) { spy('factory') }
let(:product) { spy('product') }
before do
allow(QA::Factory::Product).to receive(:new).and_return(product)
end
it 'instantiates the factory and calls factory method' do
expect(subject).to receive(:new).and_return(factory)
subject.fabricate!('something')
expect(factory).to have_received(:fabricate!).with('something')
end
it 'returns fabrication product' do
allow(subject).to receive(:new).and_return(factory)
allow(factory).to receive(:fabricate!).and_return('something')
result = subject.fabricate!('something')
expect(result).to eq product
end
it 'yields factory before calling factory method' do
allow(subject).to receive(:new).and_return(factory)
subject.fabricate! do |factory|
factory.something!
end
expect(factory).to have_received(:something!).ordered
expect(factory).to have_received(:fabricate!).ordered
end
end
describe '.dependency' do
let(:dependency) { spy('dependency') }
before do
stub_const('Some::MyDependency', dependency)
end
subject do
Class.new(described_class) do
dependency Some::MyDependency, as: :mydep do |factory|
factory.something!
end
end
end
it 'appends a new dependency and accessors' do
expect(subject.dependencies).to be_one
end
it 'defines dependency accessors' do
expect(subject.new).to respond_to :mydep, :mydep=
end
end
describe 'building dependencies' do
let(:dependency) { double('dependency') }
let(:instance) { spy('instance') }
subject do
Class.new(described_class) do
dependency Some::MyDependency, as: :mydep
end
end
before do
stub_const('Some::MyDependency', dependency)
allow(subject).to receive(:new).and_return(instance)
allow(instance).to receive(:mydep).and_return(nil)
allow(QA::Factory::Product).to receive(:new)
end
it 'builds all dependencies first' do
expect(dependency).to receive(:fabricate!).once
subject.fabricate!
end
end
end

View file

@ -0,0 +1,59 @@
describe QA::Factory::Dependency do
let(:dependency) { spy('dependency' ) }
let(:factory) { spy('factory') }
let(:block) { spy('block') }
let(:signature) do
double('signature', factory: dependency, block: block)
end
subject do
described_class.new(:mydep, factory, signature)
end
describe '#overridden?' do
it 'returns true if factory has overridden dependency' do
allow(factory).to receive(:mydep).and_return('something')
expect(subject).to be_overridden
end
it 'returns false if dependency has not been overridden' do
allow(factory).to receive(:mydep).and_return(nil)
expect(subject).not_to be_overridden
end
end
describe '#build!' do
context 'when dependency has been overridden' do
before do
allow(subject).to receive(:overridden?).and_return(true)
end
it 'does not fabricate dependency' do
subject.build!
expect(dependency).not_to have_received(:fabricate!)
end
end
context 'when dependency has not been overridden' do
before do
allow(subject).to receive(:overridden?).and_return(false)
end
it 'fabricates dependency' do
subject.build!
expect(dependency).to have_received(:fabricate!)
end
it 'sets product in the factory' do
subject.build!
expect(factory).to have_received(:mydep=).with(dependency)
end
end
end
end

View file

@ -0,0 +1,44 @@
describe QA::Factory::Product do
let(:factory) { spy('factory') }
let(:product) { spy('product') }
describe '.populate!' do
it 'instantiates and yields factory' do
expect(described_class).to receive(:new).with(factory)
described_class.populate!(factory) do |instance|
instance.something = 'string'
end
expect(factory).to have_received(:something=).with('string')
end
it 'returns a fabrication product' do
expect(described_class).to receive(:new)
.with(factory).and_return(product)
result = described_class.populate!(factory) do |instance|
instance.something = 'string'
end
expect(result).to be product
end
it 'raises unless block given' do
expect { described_class.populate!(factory) }
.to raise_error ArgumentError
end
end
describe '.visit!' do
it 'makes it possible to visit fabrication product' do
allow_any_instance_of(described_class)
.to receive(:current_url).and_return('some url')
allow_any_instance_of(described_class)
.to receive(:visit).and_return('visited some url')
expect(described_class.new(factory).visit!)
.to eq 'visited some url'
end
end
end