我有一些使用Nokogiri制作AR实例的服务对象。我创建了一个rake任务,以便我可以使用cron作业更新实例。我想测试的是,它是否正在添加以前不存在的项目,即:
Importer
url
创建spec/fixtures/feed.xml
,feed.xml包含10个项目。Show.count == 1
和Episode.count == 10
spec/fixtures/feed.xml
以包含11个项目Show.count == 1
和Episode.count == 11
我如何在RSpec中测试它,或者修改我的代码以使其更易于测试?
# models/importer.rb
class Importer < ActiveRecord::Base
after_create :parse_importer
validates :title, presence: true
validates :url, presence: true
validates :feed_format, presence: true
private
def parse_importer
Parser.new(self)
end
end
# models/show.rb
class Show < ActiveRecord::Base
validates :title, presence: true
validates :title, uniqueness: true
has_many :episodes
attr_accessor :entries
end
# models/episode.rb
class Episode < ActiveRecord::Base
validates :title, presence: true
validates :title, uniqueness: true
belongs_to :show
end
#lib/tasks/admin.rake
namespace :admin do
desc "Checks all Importer URLs for new items."
task refresh: :environment do
@importers = Importer.all
@importers.each do |importer|
Parser.new(importer)
end
end
end
# services/parser.rb
class Parser
def initialize(importer)
feed = Feed.new(importer)
show = Show.where(rss_link: importer.url).first
if show # add new episodes
new_episodes = Itunes::Channel.refresh(feed.origin)
new_episodes.each do |new_episode|
show.episodes.create feed.episode(new_episode)
end
else # create a show and its episodes
new_show = Show.new(feed.show) if (feed && feed.show)
if (new_show.save && new_show.entries.any?)
new_show.entries.each do |entry|
new_show.episodes.create feed.episode(entry)
end
end
end
end
end
# services/feed.rb
class Feed
require "nokogiri"
require "open-uri"
require "formats/itunes"
attr_reader :params, :origin, :show, :episode
def initialize(params)
@params = params
end
def origin
@origin = Nokogiri::XML(open(params[:url]))
end
def format
@format = params[:feed_format]
end
def show
case format
when "iTunes"
Itunes::Channel.fresh(origin)
end
end
def episode(entry)
@entry = entry
case format
when "iTunes"
Itunes::Item.fresh(@entry)
end
end
end
# services/formats/itunes.rb
class Itunes
class Channel
def initialize(origin)
@origin = origin
end
def title
@origin.xpath("//channel/title").text
end
def description
@origin.xpath("//channel/description").text
end
def summary
@origin.xpath("//channel/*[name()='itunes:summary']").text
end
def subtitle
@origin.xpath("//channel/*[name()='itunes:subtitle']/text()").text
end
def rss_link
@origin.xpath("//channel/*[name()='atom:link']/@href").text
end
def main_link
@origin.xpath("//channel/link/text()").text
end
def docs_link
@origin.xpath("//channel/docs/text()").text
end
def release
@origin.xpath("//channel/pubDate/text()").text
end
def image
@origin.xpath("//channel/image/url/text()").text
end
def language
@origin.xpath("//channel/language/text()").text
end
def keywords
keywords_array(@origin)
end
def categories
category_array(@origin)
end
def explicit
explicit_check(@origin)
end
def entries
entry_array(@origin)
end
def self.fresh(origin)
@show = Itunes::Channel.new origin
return {
description: @show.description,
release: @show.release,
explicit: @show.explicit,
language: @show.language,
title: @show.title,
summary: @show.summary,
subtitle: @show.subtitle,
image: @show.image,
rss_link: @show.rss_link,
main_link: @show.main_link,
docs_link: @show.docs_link,
categories: @show.categories,
keywords: @show.keywords,
entries: @show.entries
}
end
def self.refresh(origin)
@show = Itunes::Channel.new origin
return @show.entries
end
private
def category_array(channel)
arr = []
channel.xpath("//channel/*[name()='itunes:category']/@text").each do |category|
arr.push(category.to_s)
end
return arr
end
def explicit_check(channel)
string = channel.xpath("//channel/*[name()='itunes:explicit']").text
if string === "yes" || string === "Yes"
true
else
false
end
end
def keywords_array(channel)
keywords = channel.xpath("//channel/*[name()='itunes:keywords']/text()").text
arr = keywords.split(",")
return arr
end
def entry_array(channel)
arr = []
channel.xpath("//item").each do |item|
arr.push(item)
end
return arr
end
end
class Item
def initialize(origin)
@origin = origin
end
def description
@origin.xpath("*[name()='itunes:subtitle']").text
end
def release
@origin.xpath("pubDate").text
end
def image
@origin.xpath("*[name()='itunes:image']/@href").text
end
def explicit
explicit_check(@origin)
end
def duration
@origin.xpath("*[name()='itunes:duration']").text
end
def title
@origin.xpath("title").text
end
def enclosure_url
@origin.xpath("enclosure/@url").text
end
def enclosure_length
@origin.xpath("enclosure/@length").text
end
def enclosure_type
@origin.xpath("enclosure/@type").text
end
def keywords
keywords_array(@origin.xpath("*[name()='itunes:keywords']").text)
end
def self.fresh(entry)
@episode = Itunes::Item.new entry
return {
description: @episode.description,
release: @episode.release,
image: @episode.image,
explicit: @episode.explicit,
duration: @episode.duration,
title: @episode.title,
enclosure_url: @episode.enclosure_url,
enclosure_length: @episode.enclosure_length,
enclosure_type: @episode.enclosure_type,
keywords: @episode.keywords
}
end
private
def explicit_check(item)
string = item.xpath("*[name()='itunes:explicit']").text
if string === "yes" || string === "Yes"
true
else
false
end
end
def keywords_array(item)
keywords = item.split(",")
return keywords
end
end
end
答案 0 :(得分:0)
在其他任何事情之前,对您使用服务对象有好处!我已经大量使用这种方法,并且在许多情况下发现PORO比胖模型更可取。
您对测试感兴趣的行为似乎包含在Parser.initialize
中。
首先,我为Parser
创建一个名为parse
的类方法。 IMO,Parser.parse(importer)
更清楚Parser
正在做什么而不是Parser.new(importer)
。所以,它可能看起来像:
#services/parser.rb
class Parser
class << self
def parse(importer)
@importer = importer
@feed = Feed.new(importer)
if @show = Show.where(rss_link: importer.url).first
create_new_episodes Itunes::Channel.refresh(@feed.origin)
else
create_show_and_episodes
end
end # parse
end
end
然后添加create_new_episodes
和create_show_and_episodes
类方法。
#services/parser.rb
class Parser
class << self
def parse(importer)
@importer = importer
@feed = Feed.new(importer)
if @show = Show.where(rss_link: @importer.url).first
create_new_episodes Itunes::Channel.refresh(@feed.origin)
else
create_show_and_episodes
end
end # parse
def create_new_episodes(new_episodes)
new_episodes.each do |new_episode|
@show.episodes.create @feed.episode(new_episode)
end
end # create_new_episodes
def create_show_and_episodes
new_show = Show.new(@feed.show) if (@feed && @feed.show)
if (new_show.save && new_show.entries.any?)
new_show.entries.each do |entry|
new_show.episodes.create @feed.episode(entry)
end
end
end # create_show_and_episodes
end
end
现在您有一个Parser.create_new_episodes
方法可以独立测试。因此,您的测试可能类似于:
require 'rspec_helper'
describe Parser do
describe '.create_new_episodes' do
context 'when an initial parse has been completed' do
before(:each) do
first_file = Nokogiri::XML(open('spec/fixtures/feed_1.xml'))
@second_file = Nokogiri::XML(open('spec/fixtures/feed_2.xml'))
Parser.create_show_and_episodes first_file
end
it 'changes Episodes.count by 1' do
expect{Parser.create_new_episodes(@second_file)}.to change{Episodes.count}.by(1)
end
it 'changes Show.count by 0' do
expect{Parser.create_new_episodes(@second_file)}.to change{Show.count}.by(0)
end
end
end
end
当然,您在feed_1.xml
目录中需要feed_2.xml
和spec\fixtures
。
对任何错别字道歉。而且,我没有运行代码。所以,可能是马车。希望它有所帮助。