{"id":354,"date":"2014-02-08T10:03:44","date_gmt":"2014-02-08T10:03:44","guid":{"rendered":"http:\/\/www.timwyatt.ca\/test\/?p=354"},"modified":"2014-02-18T20:57:52","modified_gmt":"2014-02-18T20:57:52","slug":"using-test-doubles-in-chefspec","status":"publish","type":"post","link":"https:\/\/www.timwyatt.ca\/test\/?p=354","title":{"rendered":"Using Test Doubles in ChefSpec"},"content":{"rendered":"<p>One of the improvements to <a href=\"http:\/\/code.sethvargo.com\/chefspec\/\">ChefSpec<\/a> in the 3.0 release is the ability to extend test coverage to execute blocks.  Doing so requires the infrastructure developer to stub out shell commands run as part of the idempotence check.  This is pretty simple as ChefSpec provides a macro to stub shell commands.  However doing so where the idempotence check uses a Ruby block is slightly more involved.  In this article I explain how to do both.<\/p>\n<h2>Quick overview of ChefSpec<\/h2>\n<p>ChefSpec is a powerful and flexible unit testing utility for Chef recipes.  Extending the popular Ruby testing tool, <a href=\"http:\/\/rspec.info\/\">Rspec<\/a>, it allows the developer to make assertions about the Chef resources declared and used in recipe code. The key concept here is that of Chef resources.  When we write Chef code to build infrastructure, we&#8217;re using Chef&#8217;s domain-specific language to declare resources &#8211; abstractions representing the components we need to build and configure.  I&#8217;m not going to provide a from-the-basics tutorial here, but dive in with a simple example.  Here&#8217;s a test that asserts that our default recipe will use the <code>package<\/code> resource to install <a href=\"http:\/\/openjdk.java.net\/\">OpenJDK<\/a>.<\/p>\n<pre><code>require 'chefspec'&#x000a;&#x000a;RSpec.configure do |config|&#x000a;  config.platform = 'centos'&#x000a;  config.version = '6.4'&#x000a;  config.color = true&#x000a;&#x000a;  describe 'stubs-and-doubles' do&#x000a;&#x000a;    let(:chef_run) {  ChefSpec::Runner.new.converge(described_recipe) }&#x000a;&#x000a;    it 'installs OpenJDK' do&#x000a;      expect(chef_run).to install_package 'java-1.7.0-openjdk'&#x000a;    end&#x000a;&#x000a;  end&#x000a;end&#x000a;<\/code><\/pre>\n<p>If we run this first, we&#8217;ll see something like this:<\/p>\n<pre><code>$ rspec -fd spec\/default_spec.rb &#x000a;&#x000a;stubs-and-doubles&#x000a;&#x000a;================================================================================&#x000a;Recipe Compile Error&#x000a;================================================================================&#x000a;&#x000a;Chef::Exceptions::RecipeNotFound&#x000a;--------------------------------&#x000a;could not find recipe default for cookbook stubs-and-doubles&#x000a;&#x000a;  installs OpenJDK (FAILED - 1)&#x000a;&#x000a;Failures:&#x000a;&#x000a;  1) stubs-and-doubles installs OpenJDK&#x000a;     Failure\/Error: let(:chef_run) {  ChefSpec::Runner.new.converge(described_recipe) }&#x000a;     Chef::Exceptions::RecipeNotFound:&#x000a;       could not find recipe default for cookbook stubs-and-doubles&#x000a;     # .\/spec\/default_spec.rb:10:in `block (3 levels) in &lt;top (required)&gt;'&#x000a;     # .\/spec\/default_spec.rb:13:in `block (3 levels) in &lt;top (required)&gt;'&#x000a;&#x000a;Finished in 0.01163 seconds&#x000a;1 example, 1 failure&#x000a;&#x000a;Failed examples:&#x000a;&#x000a;rspec .\/spec\/default_spec.rb:12 # stubs-and-doubles installs OpenJDK&#x000a;<\/code><\/pre>\n<p>This is reasonable &#8211; I&#8217;ve not even written the recipe yet.  Once I add the default recipe, such as:<\/p>\n<pre><code>package 'java-1.7.0-openjdk'&#x000a;<\/code><\/pre>\n<p>Now the test passes:<\/p>\n<pre><code>$ rspec -fd spec\/default_spec.rb &#x000a;&#x000a;&#x000a;stubs-and-doubles&#x000a;  installs OpenJDK&#x000a;&#x000a;Finished in 0.01215 seconds&#x000a;1 example, 0 failures&#x000a;<\/code><\/pre>\n<h2>Test Doubles<\/h2>\n<p>ChefSpec works by running a fake Chef run, and checking that the resources were called, with the correct parameters.  Behind the scenes, your cookbooks are loaded, but instead of performing real actions on the system, the Chef Resource class is modified such that messages are sent to ChefSpec instead.  One of the key principles of Chef is that resources should be <em>idempotent<\/em> &#8211; the action should only be taken if required, and it&#8217;s safe to rerun the resource.  In most cases, the Chef provider knows how to guarantee this &#8211; it knows how to check that a package was installed, that a directory was created.  However, if we use an execute resource &#8211; a resource where we&#8217;re calling directly to the underlying operating system &#8211; Chef has no way to tell if the command we called did the right thing.  Unless we explicitly tell Chef how to check, it will just run the command again and again.  This causes a headache for ChefSpec, because it doesn&#8217;t have a built-in mechanism for faking operating system calls &#8211; so when it comes across a guard, it requires us to help it out, by stubbing the command.<\/p>\n<p>This introduces some testing vocabulary &#8211; vocabulary that is worth stating explicitly, for the avoidance of confusion.  I&#8217;m a fan of the approach described in <a href=\"https:\/\/twitter.com\/gerardmes\">Gerard Meszaros&#8217;<\/a> 2007 book <a href=\"http:\/\/www.pearsonhighered.com\/educator\/product\/xUnit-Test-Patterns-Refactoring-Test-Code\/9780131495050.page\"><em>xUnit Test Patterns: Refactoring Test Code<\/em><\/a>, and this is the terminology used by Rspec.  Let&#8217;s itemise a quick glossary:<\/p>\n<ul>\n<li><code>System Under Test (SUT)<\/code> &#8211; this is the thing we&#8217;re actually testing.  In our case, we&#8217;re testing the resources in a Chef recipe.  Note we&#8217;re explictly <em>not<\/em> testing the operating system.<\/li>\n<li><code>Depended-on Component (DOC)<\/code> &#8211; usually our SUT has some external dependency &#8211; a database, a third-party API, or on our case, the operating system.  This is an example of a <code>DOC<\/code><\/li>\n<li><code>Test Double<\/code> &#8211; when unit testing, we don&#8217;t want to make real calls to the <code>DOC<\/code>.  It&#8217;s slow, can introduce unwanted variables into our systems, and if it becomes unavailable our tests won&#8217;t run, or will fail.  Instead we want to be able to interact with something that represents the DOC.  The family of approaches to implement this abstraction is commonly referred to as <code>Test Doubles<\/code>.<\/li>\n<li><code>Stubbing<\/code> &#8211; when our SUT depends on some input from the DOC, we need to be able to control those.  A typical approach is to <code>stub<\/code> the method that makes a call to the DOC, typically returning some canned data.<\/li>\n<\/ul>\n<p>Let&#8217;s look at a real example.  The <a href=\"https:\/\/github.com\/hw-cookbooks\/runit\">community runit cookbook<\/a>, when run on a RHEL derivative, will, by default, build an RPM and install it.  The code to accomplish this looks like this:<\/p>\n<pre><code>bash 'rhel_build_install' do&#x000a;      user 'root'&#x000a;      cwd Chef::Config[:file_cache_path]&#x000a;      code &lt;&lt;-EOH&#x000a;tar xzf runit-2.1.1.tar.gz&#x000a;cd runit-2.1.1&#x000a;.\/build.sh&#x000a;rpm_root_dir=`rpm --eval '%{_rpmdir}'`&#x000a;rpm -ivh '\/root\/rpmbuild\/RPMS\/runit-2.1.1.rpm'&#x000a;EOH&#x000a;      action :run&#x000a;      not_if rpm_installed&#x000a;end&#x000a;<\/code><\/pre>\n<p>Observe the guard &#8211; <code>not_if rpm_installed<\/code>.  Earlier in the recipe, that method is defined as:<\/p>\n<pre><code>rpm_installed = \"rpm -qa | grep -q '^runit'\"&#x000a;<\/code><\/pre>\n<p>ChefSpec can&#8217;t handle direct OS calls, and so if we include the <a href=\"http:\/\/smarden.org\/runit\/\">runit<\/a> cookbook in our recipe, we&#8217;ll get an error.  Let&#8217;s start by writing a simple test that asserts that we include the runit recipe.  I&#8217;m going to use <a href=\"http:\/\/berkshelf.com\/\">Berkshelf<\/a> as my dependency solver, which means I need to add a dependency in my cookbook metadata, and supply a Berksfile that tells Berkshelf to check the metadata for dependencies.  I also need to add Berkshelf support to my test.  My test now looks like this:<\/p>\n<pre><code>require 'chefspec'&#x000a;require 'chefspec\/berkshelf'&#x000a;&#x000a;RSpec.configure do |config|&#x000a;  config.platform = 'centos'&#x000a;  config.version = '6.4'&#x000a;  config.color = true&#x000a;&#x000a;  describe 'stubs-and-doubles' do&#x000a;&#x000a;    let(:chef_run) {  ChefSpec::Runner.new.converge(described_recipe) }&#x000a;&#x000a;    it 'includes the runit recipe' do&#x000a;      expect(chef_run).to include_recipe 'runit'&#x000a;    end&#x000a;&#x000a;    it 'installs OpenJDK' do&#x000a;      expect(chef_run).to install_package 'java-1.7.0-openjdk'&#x000a;    end&#x000a;&#x000a;  end&#x000a;end&#x000a;<\/code><\/pre>\n<p>And my recipe like this:<\/p>\n<pre><code>include_recipe 'runit'&#x000a;package 'java-1.7.0-openjdk'&#x000a;<\/code><\/pre>\n<p>Now, when I run the test, ChefSpec complains:<\/p>\n<pre><code>1) stubs-and-doubles includes the runit recipe&#x000a;     Failure\/Error: let(:chef_run) {  ChefSpec::Runner.new.converge(described_recipe) }&#x000a;     ChefSpec::Error::CommandNotStubbed:&#x000a;       Executing a real command is disabled. Unregistered command: `command(\"rpm -qa | grep -q '^runit'\")`\"&#x000a;&#x000a;       You can stub this command with:&#x000a;&#x000a;         stub_command(\"rpm -qa | grep -q '^runit'\").and_return(...)&#x000a;     # .\/spec\/default_spec.rb:11:in `block (3 levels) in &lt;top (required)&gt;'&#x000a;     # .\/spec\/default_spec.rb:14:in `block (3 levels) in &lt;top (required)&gt;'&#x000a;<\/code><\/pre>\n<p>ChefSpec tells us exactly what we need to do, but let&#8217;s unpack it a little, using the vocabulary from above.  The SUT, our <code>stunts-and-doubles<\/code> cookbook, has a dependency on the operating system &#8211; the DOC.  This means we need to be able to insert a test double of the operating system, specifically a test stub, which will provide a canned answer to our rpm command.  ChefSpec makes it very easy for us by providing a macro that does exactly this.  We need to run this before every example, so we can put it in a before block.  The new test now looks like this:<\/p>\n<pre><code>require 'chefspec'&#x000a;require 'chefspec\/berkshelf'&#x000a;&#x000a;RSpec.configure do |config|&#x000a;  config.platform = 'centos'&#x000a;  config.version = '6.4'&#x000a;  config.color = true&#x000a;&#x000a;  describe 'stubs-and-doubles' do&#x000a;&#x000a;    before(:each) do&#x000a;      stub_command(\"rpm -qa | grep -q '^runit'\").and_return(true)&#x000a;    end&#x000a;&#x000a;    let(:chef_run) {  ChefSpec::Runner.new.converge(described_recipe) }&#x000a;&#x000a;    it 'includes the runit recipe' do&#x000a;      expect(chef_run).to include_recipe 'runit'&#x000a;    end&#x000a;&#x000a;    it 'installs OpenJDK' do&#x000a;      expect(chef_run).to install_package 'java-1.7.0-openjdk'&#x000a;    end&#x000a;&#x000a;  end&#x000a;end&#x000a;<\/code><\/pre>\n<p>Now when we run the test, it passes:<\/p>\n<pre><code>$ rspec -fd spec\/default_spec.rb &#x000a;&#x000a;stubs-and-doubles&#x000a;  includes the runit recipe&#x000a;  installs OpenJDK&#x000a;&#x000a;Finished in 0.57793 seconds&#x000a;2 examples, 0 failures&#x000a;<\/code><\/pre>\n<p>That&#8217;s all fine and dandy, but suppose we execute some Ruby for our guard instead of a shell command.  Here&#8217;s an example from one of my cookbooks, in which I <a href=\"http:\/\/wiki.centos.org\/TipsAndTricks\/SelinuxBooleans\">set the correct Selinux policy<\/a> to allow apache to proxy to a locally running <a href=\"http:\/\/netty.io\/\">Netty<\/a> server:<\/p>\n<pre><code>unless (node['platform'] == 'Amazon' or node['web_proxy']['selinux'] == 'Disabled')&#x000a;  execute 'Allow Apache Network Connection in SELinux' do&#x000a;    command '\/usr\/sbin\/setsebool -P httpd_can_network_connect 1'&#x000a;    not_if { Mixlib::ShellOut.new('getsebool httpd_can_network_connect').run_command.stdout.match(\/--&gt; on\/) }&#x000a;    notifies :restart, 'service[httpd]'&#x000a;  end&#x000a;end&#x000a;<\/code><\/pre>\n<p>Now, OK, I could have used grep, but I prefer this approach, and it&#8217;s a good enough example to illustrate how we handle this kind of case in ChefSpec.  First, let&#8217;s write a test:<\/p>\n<pre><code>it 'sets the Selinux policy to allow proxying to localhost' do&#x000a;  expect(chef_run).to run_execute('Allow Apache Network Connection in SELinux')&#x000a;  resource = chef_run.execute('Allow Apache Network Connection in SELinux')&#x000a;  expect(resource).to notify('service[httpd]').to(:restart)&#x000a;end&#x000a;<\/code><\/pre>\n<p>If we were to run this, ChefSpec would complain that we didn&#8217;t have an execute resource with a run action on our run list.  So we then add the execute block from above to the default recipe.  I&#8217;m going to omit the platform check for simplicity, and just include the execute resource.  We&#8217;re also going to need to define an httpd service.  Of course we&#8217;re never going to actually run this code, so I&#8217;m not fussed that the service exists despite us never installing <a href=\"http:\/\/httpd.apache.org\/\">Apache<\/a>.  My concern in this article is to teach you about the testing, not write a trivial and pointless cookbook.<\/p>\n<p>Now our recipe looks like this:<\/p>\n<pre><code>include_recipe 'runit'&#x000a;package 'java-1.7.0-openjdk'&#x000a;&#x000a;service 'httpd'&#x000a;&#x000a;execute 'Allow Apache Network Connection in SELinux' do&#x000a;  command '\/usr\/sbin\/setsebool -P httpd_can_network_connect 1'&#x000a;  not_if { Mixlib::ShellOut.new('getsebool httpd_can_network_connect').run_command.stdout.match(\/--&gt; on\/) }&#x000a;  notifies :restart, 'service[httpd]'&#x000a;end&#x000a;<\/code><\/pre>\n<p>When we run the test, we&#8217;d expect all to be fine.  We&#8217;re asserting that there&#8217;s an execute resource, that runs, and that it notifies the httpd service to restart.  However, this is what we see:<\/p>\n<pre><code>Failures:&#x000a;&#x000a;  1) stubs-and-doubles includes the runit recipe&#x000a;     Failure\/Error: let(:chef_run) {  ChefSpec::Runner.new.converge(described_recipe) }&#x000a;     Errno::ENOENT:&#x000a;       No such file or directory - getsebool httpd_can_network_connect&#x000a;     # \/tmp\/d20140208-30704-g1s3d4\/stubs-and-doubles\/recipes\/default.rb:8:in `block (2 levels) in from_file'&#x000a;     # .\/spec\/default_spec.rb:20:in `block (3 levels) in &lt;top (required)&gt;'&#x000a;     # .\/spec\/default_spec.rb:23:in `block (3 levels) in &lt;top (required)&gt;'&#x000a;&#x000a;  2) stubs-and-doubles installs OpenJDK&#x000a;     Failure\/Error: let(:chef_run) {  ChefSpec::Runner.new.converge(described_recipe) }&#x000a;     Errno::ENOENT:&#x000a;       No such file or directory - getsebool httpd_can_network_connect&#x000a;     # \/tmp\/d20140208-30704-g1s3d4\/stubs-and-doubles\/recipes\/default.rb:8:in `block (2 levels) in from_file'&#x000a;     # .\/spec\/default_spec.rb:20:in `block (3 levels) in &lt;top (required)&gt;'&#x000a;     # .\/spec\/default_spec.rb:27:in `block (3 levels) in &lt;top (required)&gt;'&#x000a;&#x000a;  3) stubs-and-doubles sets the Selinux policy to allow proxying to localhost&#x000a;     Failure\/Error: let(:chef_run) {  ChefSpec::Runner.new.converge(described_recipe) }&#x000a;     Errno::ENOENT:&#x000a;       No such file or directory - getsebool httpd_can_network_connect&#x000a;     # \/tmp\/d20140208-30704-g1s3d4\/stubs-and-doubles\/recipes\/default.rb:8:in `block (2 levels) in from_file'&#x000a;     # .\/spec\/default_spec.rb:20:in `block (3 levels) in &lt;top (required)&gt;'&#x000a;     # .\/spec\/default_spec.rb:31:in `block (3 levels) in &lt;top (required)&gt;'&#x000a;&#x000a;Finished in 1.11 seconds&#x000a;3 examples, 3 failures&#x000a;<\/code><\/pre>\n<p>Boom!  What&#8217;s wrong?  Well, ChefSpec isn&#8217;t smart enough to warn us about the guard we tried to run, and actually tries to run the Ruby block.  I&#8217;m (deliberately) running this on a machine without the ability to run the <code>getsebool<\/code> command to trigger this response, but on my usual workstation running <a href=\"https:\/\/fedoraproject.org\/wiki\/F20_release_announcement\">Fedora<\/a>, this will silently pass.  This is what prompted me to write this article, because my colleague who runs these tests on his mac kept getting this <em>No such file or directory &#8211; getsebool httpd_can_network_connect<\/em> error, despite the Jenkins box (running CentOS) and my workstation working just fine.  So &#8211; what&#8217;s the solution?  Well, we need to do something similar to that which ChefSpec did for us earlier.  We need to create a test double, only this time it&#8217;s <a href=\"http:\/\/github.com\/opscode\/mixlib-shellout\">Mixlib::ShellOut<\/a> that we need to stub.  There are three steps we need to follow.  We need to capture the :new method  that is called on Mixlib::ShellOut, and instead of returning canned data, like we did when we called <code>stub_command<\/code>, we want to return the test double, standing in for the real instance of <a href=\"http:\/\/github.com\/opscode\/mixlib-shellout\">Mixlib::Shellout<\/a>, and finally we want to control the behaviour of the test double, making it return the output we want for out test.  So, first we need to create the test double.  We do that with the <code>double<\/code> method in Rspec:<\/p>\n<pre><code>shellout = double&#x000a;<\/code><\/pre>\n<p>This just gives us a blank test double &#8211; we can do anything we like with it.  Now we need to stub the constructor, and return the double:<\/p>\n<pre><code>Mixlib::ShellOut.stub(:new).and_return(shellout)&#x000a;<\/code><\/pre>\n<p>Finally, we specify how the shellout double should respond when it receives the :run_command method.<\/p>\n<pre><code>  allow(shellout).to receive(:run_command).and_return('--&gt; off')&#x000a;<\/code><\/pre>\n<p>We want the double to return a string that won&#8217;t cause the guard to be triggered, because we want to assert that the execute method is called.  We can add these three lines to the before block:<\/p>\n<pre><code>before(:each) do&#x000a;  stub_command(\"rpm -qa | grep -q '^runit'\").and_return(true)&#x000a;  shellout = double&#x000a;  Mixlib::ShellOut.stub(:new).and_return(shellout)&#x000a;  allow(shellout).to receive(:run_command).and_return('--&gt; off')&#x000a;end&#x000a;<\/code><\/pre>\n<p>Now when we run the test, we&#8217;d expect the Mixlib guard to be stubbed, the test double returned, and the test double to respond to having the :run_command method called be that it returns a string which doesn&#8217;t match the guard, and thus the execute should run!  Let&#8217;s give it a try:<\/p>\n<pre><code>Failures:&#x000a;&#x000a;  1) stubs-and-doubles includes the runit recipe&#x000a;     Failure\/Error: let(:chef_run) {  ChefSpec::Runner.new.converge(described_recipe) }&#x000a;     NoMethodError:&#x000a;       undefined method `stdout' for \"--&gt; off\":String&#x000a;     # \/tmp\/d20140208-30741-eynz5u\/stubs-and-doubles\/recipes\/default.rb:8:in `block (2 levels) in from_file'&#x000a;     # .\/spec\/default_spec.rb:20:in `block (3 levels) in &lt;top (required)&gt;'&#x000a;     # .\/spec\/default_spec.rb:23:in `block (3 levels) in &lt;top (required)&gt;'&#x000a;<\/code><\/pre>\n<p>Alas!  What have we done wrong?  Look closely at the error.  Ruby tried to call :stdout on a String.  Why did it do that?  Look at the guard again:<\/p>\n<pre><code>not_if { Mixlib::ShellOut.new('getsebool httpd_can_network_connect').run_command.stdout.match(\/--&gt; on\/) }&#x000a;<\/code><\/pre>\n<p>Aha&#8230; we need another double.  When the first double is called, we need to return something that can accept a stdout call, which in turn will return the string.  Let&#8217;s add that in:<\/p>\n<pre><code>before(:each) do&#x000a;  stub_command(\"rpm -qa | grep -q '^runit'\").and_return(true)&#x000a;  shellout = double&#x000a;  getsebool = double&#x000a;  Mixlib::ShellOut.stub(:new).and_return(shellout)&#x000a;  allow(shellout).to receive(:run_command).and_return(getsebool)&#x000a;  allow(getsebool).to receive(:stdout).and_return('--&gt; off')&#x000a;end&#x000a;<\/code><\/pre>\n<p>Once more with feeling:<\/p>\n<pre><code>$ bundle exec rspec -fd spec\/default_spec.rb &#x000a;&#x000a;stubs-and-doubles&#x000a;  includes the runit recipe&#x000a;  installs OpenJDK&#x000a;  sets the Selinux policy to allow proxying to localhost&#x000a;&#x000a;Finished in 0.7313 seconds&#x000a;3 examples, 0 failures&#x000a;<\/code><\/pre>\n<p>Just to illustrate how the double interacts with the test, let&#8217;s quickly change what <code>getsebool<\/code> returns:<\/p>\n<pre><code>allow(getsebool).to receive(:stdout).and_return('--&gt; on')&#x000a;<\/code><\/pre>\n<p>Now when we rerun the test, it fails:<\/p>\n<pre><code>Failures:&#x000a;&#x000a;  1) stubs-and-doubles sets the Selinux policy to allow proxying to localhost&#x000a;     Failure\/Error: expect(chef_run).to run_execute('Allow Apache Network Connection in SELinux')&#x000a;       expected \"execute[Allow Apache Network Connection in SELinux]\" actions [] to include :run&#x000a;     # .\/spec\/default_spec.rb:31:in `block (3 levels) in &lt;top (required)&gt;'&#x000a;<\/code><\/pre>\n<p>This time the guard prevented the execute from running, and as such the resource collection didn&#8217;t contain this resource, and so the test failed.<\/p>\n<h2>Conclusion<\/h2>\n<p>One of the great beauties of ChefSpec (and of course Chef) is that at its heart it&#8217;s just Ruby.  This means that at almost any point you can reach into the standard Ruby development toolkit for your testing or infrastructure development needs.  Hopefully this little example will be helpful to you.  If it inspires you to read more about mocking, I can recommend the following resources:<\/p>\n<ul>\n<li><a href=\"http:\/\/pragprog.com\/book\/achbd\/the-rspec-book\">Rspec book<\/a><\/li>\n<li><a href=\"http:\/\/pivotallabs.com\/geek-glossary-stub\/\">The Geek Glossary: Stubs<\/a><\/li>\n<li><a href=\"http:\/\/pivotallabs.com\/means-ends-mocks-stubs\/\">Means and Ends \/\/ Mocks and Stubs<\/a><\/li>\n<li><a href=\"http:\/\/martinfowler.com\/articles\/mocksArentStubs.html\">Mocks aren&#8217;t Stubs!<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>One of the improvements to ChefSpec in the 3.0 release is the ability to extend test coverage to execute blocks. Doing so requires the infrastructure developer to stub out shell commands run as part of&hellip;<\/p>\n<p><a class=\"readmore\" href=\"https:\/\/www.timwyatt.ca\/test\/?p=354\">Read More<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[],"class_list":["post-354","post","type-post","status-publish","format-standard","hentry","category-ziba","comments-off"],"_links":{"self":[{"href":"https:\/\/www.timwyatt.ca\/test\/index.php?rest_route=\/wp\/v2\/posts\/354"}],"collection":[{"href":"https:\/\/www.timwyatt.ca\/test\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.timwyatt.ca\/test\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.timwyatt.ca\/test\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.timwyatt.ca\/test\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=354"}],"version-history":[{"count":1,"href":"https:\/\/www.timwyatt.ca\/test\/index.php?rest_route=\/wp\/v2\/posts\/354\/revisions"}],"predecessor-version":[{"id":355,"href":"https:\/\/www.timwyatt.ca\/test\/index.php?rest_route=\/wp\/v2\/posts\/354\/revisions\/355"}],"wp:attachment":[{"href":"https:\/\/www.timwyatt.ca\/test\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=354"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.timwyatt.ca\/test\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=354"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.timwyatt.ca\/test\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=354"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}