chefspec 7.2.1 → 7.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (342) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +21 -13
  3. data/CHANGELOG.md +4 -0
  4. data/Gemfile +14 -10
  5. data/README.md +322 -1025
  6. data/README_old.md +1291 -0
  7. data/Rakefile +8 -3
  8. data/examples/apt_package/spec/install_spec.rb +1 -1
  9. data/examples/apt_package/spec/lock_spec.rb +1 -1
  10. data/examples/apt_package/spec/purge_spec.rb +1 -1
  11. data/examples/apt_package/spec/reconfig_spec.rb +1 -1
  12. data/examples/apt_package/spec/remove_spec.rb +1 -1
  13. data/examples/apt_package/spec/unlock_spec.rb +1 -1
  14. data/examples/apt_package/spec/upgrade_spec.rb +1 -1
  15. data/examples/apt_repository/spec/add_spec.rb +1 -1
  16. data/examples/apt_repository/spec/remove_spec.rb +1 -1
  17. data/examples/apt_update/spec/periodic_spec.rb +1 -1
  18. data/examples/apt_update/spec/update_spec.rb +1 -1
  19. data/examples/attributes/spec/default_spec.rb +1 -1
  20. data/examples/batch/spec/run_spec.rb +1 -1
  21. data/examples/cab_package/spec/install_spec.rb +1 -1
  22. data/examples/cab_package/spec/remove_spec.rb +1 -1
  23. data/examples/cached/spec/default_spec.rb +1 -1
  24. data/examples/chef_gem/spec/install_spec.rb +1 -1
  25. data/examples/chef_gem/spec/purge_spec.rb +1 -1
  26. data/examples/chef_gem/spec/reconfig_spec.rb +1 -1
  27. data/examples/chef_gem/spec/remove_spec.rb +1 -1
  28. data/examples/chef_gem/spec/upgrade_spec.rb +1 -1
  29. data/examples/chocolatey_package/spec/install_spec.rb +1 -1
  30. data/examples/chocolatey_package/spec/remove_spec.rb +1 -1
  31. data/examples/chocolatey_package/spec/upgrade_spec.rb +1 -1
  32. data/examples/compile_time/spec/default_spec.rb +1 -1
  33. data/examples/cookbook_file/spec/create_if_missing_spec.rb +1 -1
  34. data/examples/cookbook_file/spec/create_spec.rb +1 -1
  35. data/examples/cookbook_file/spec/delete_spec.rb +1 -1
  36. data/examples/cookbook_file/spec/touch_spec.rb +1 -1
  37. data/examples/core/recipes/default.rb +1 -0
  38. data/examples/core/spec/default_spec.rb +7 -0
  39. data/examples/core/spec/non_recipe_spec.rb +5 -0
  40. data/examples/cron/spec/create_spec.rb +1 -1
  41. data/examples/cron/spec/delete_spec.rb +1 -1
  42. data/examples/custom_matcher/spec/install_spec.rb +1 -1
  43. data/examples/custom_matcher/spec/remove_spec.rb +1 -1
  44. data/examples/custom_resource/spec/default_spec.rb +1 -1
  45. data/examples/custom_resource_block/resources/default.rb +3 -0
  46. data/examples/custom_resource_block/spec/default_spec.rb +10 -0
  47. data/examples/directory/spec/create_spec.rb +1 -1
  48. data/examples/directory/spec/delete_spec.rb +1 -1
  49. data/examples/dnf_package/spec/install_spec.rb +1 -1
  50. data/examples/dnf_package/spec/purge_spec.rb +1 -1
  51. data/examples/dnf_package/spec/remove_spec.rb +1 -1
  52. data/examples/dnf_package/spec/upgrade_spec.rb +1 -1
  53. data/examples/do_nothing/spec/default_spec.rb +1 -1
  54. data/examples/dpkg_package/spec/install_spec.rb +1 -1
  55. data/examples/dpkg_package/spec/purge_spec.rb +1 -1
  56. data/examples/dpkg_package/spec/remove_spec.rb +1 -1
  57. data/examples/dsc_resource/spec/run_spec.rb +1 -1
  58. data/examples/dsc_script/spec/run_spec.rb +1 -1
  59. data/examples/env/spec/create_spec.rb +1 -1
  60. data/examples/env/spec/delete_spec.rb +1 -1
  61. data/examples/env/spec/modify_spec.rb +1 -1
  62. data/examples/execute/spec/run_spec.rb +1 -1
  63. data/examples/expect_exception/spec/compile_error_spec.rb +1 -1
  64. data/examples/expect_exception/spec/converge_error_spec.rb +1 -1
  65. data/examples/expect_exception/spec/no_error_spec.rb +1 -1
  66. data/examples/file/spec/create_if_missing_spec.rb +1 -1
  67. data/examples/file/spec/create_spec.rb +1 -1
  68. data/examples/file/spec/delete_spec.rb +1 -1
  69. data/examples/file/spec/touch_spec.rb +1 -1
  70. data/examples/freebsd_package/spec/install_spec.rb +1 -1
  71. data/examples/freebsd_package/spec/remove_spec.rb +1 -1
  72. data/examples/gem_package/spec/install_spec.rb +1 -1
  73. data/examples/gem_package/spec/purge_spec.rb +1 -1
  74. data/examples/gem_package/spec/reconfig_spec.rb +1 -1
  75. data/examples/gem_package/spec/remove_spec.rb +1 -1
  76. data/examples/gem_package/spec/upgrade_spec.rb +1 -1
  77. data/examples/git/spec/checkout_spec.rb +1 -1
  78. data/examples/git/spec/export_spec.rb +1 -1
  79. data/examples/git/spec/sync_spec.rb +1 -1
  80. data/examples/group/spec/create_spec.rb +1 -1
  81. data/examples/group/spec/manage_spec.rb +1 -1
  82. data/examples/group/spec/modify_spec.rb +1 -1
  83. data/examples/group/spec/remove_spec.rb +1 -1
  84. data/examples/guards/spec/default_spec.rb +1 -1
  85. data/examples/heavy_provider_light_resource/spec/provider_service_spec.rb +1 -1
  86. data/examples/homebrew_package/spec/install_spec.rb +1 -1
  87. data/examples/homebrew_package/spec/purge_spec.rb +1 -1
  88. data/examples/homebrew_package/spec/remove_spec.rb +1 -1
  89. data/examples/homebrew_package/spec/upgrade_spec.rb +1 -1
  90. data/examples/http_request/spec/delete_spec.rb +1 -1
  91. data/examples/http_request/spec/get_spec.rb +1 -1
  92. data/examples/http_request/spec/head_spec.rb +1 -1
  93. data/examples/http_request/spec/options_spec.rb +1 -1
  94. data/examples/http_request/spec/post_spec.rb +1 -1
  95. data/examples/http_request/spec/put_spec.rb +1 -1
  96. data/examples/ifconfig/spec/add_spec.rb +1 -1
  97. data/examples/ifconfig/spec/delete_spec.rb +1 -1
  98. data/examples/ifconfig/spec/disable_spec.rb +1 -1
  99. data/examples/ifconfig/spec/enable_spec.rb +1 -1
  100. data/examples/include_recipe/spec/default_spec.rb +1 -1
  101. data/examples/ips_package/spec/install_spec.rb +1 -1
  102. data/examples/ips_package/spec/remove_spec.rb +1 -1
  103. data/examples/ips_package/spec/upgrade_spec.rb +1 -1
  104. data/examples/launchd/spec/create_if_missing_spec.rb +1 -1
  105. data/examples/launchd/spec/create_spec.rb +1 -1
  106. data/examples/launchd/spec/delete_spec.rb +1 -1
  107. data/examples/launchd/spec/disable_spec.rb +1 -1
  108. data/examples/launchd/spec/enable_spec.rb +1 -1
  109. data/examples/library_patch/libraries/default.rb +5 -0
  110. data/examples/library_patch/recipes/default.rb +1 -0
  111. data/examples/library_patch/spec/default_spec.rb +14 -0
  112. data/examples/link/spec/create_spec.rb +1 -1
  113. data/examples/link/spec/delete_spec.rb +1 -1
  114. data/examples/link/spec/link_to_spec.rb +1 -1
  115. data/examples/log/spec/write_spec.rb +1 -1
  116. data/examples/macports_package/spec/install_spec.rb +1 -1
  117. data/examples/macports_package/spec/purge_spec.rb +1 -1
  118. data/examples/macports_package/spec/remove_spec.rb +1 -1
  119. data/examples/macports_package/spec/upgrade_spec.rb +1 -1
  120. data/examples/mdadm/spec/assemble_spec.rb +1 -1
  121. data/examples/mdadm/spec/create_spec.rb +1 -1
  122. data/examples/mdadm/spec/stop_spec.rb +1 -1
  123. data/examples/mount/spec/disable_spec.rb +1 -1
  124. data/examples/mount/spec/enable_spec.rb +1 -1
  125. data/examples/mount/spec/mount_spec.rb +1 -1
  126. data/examples/mount/spec/remount_spec.rb +1 -1
  127. data/examples/mount/spec/umount_spec.rb +1 -1
  128. data/examples/msu_package/spec/install_spec.rb +1 -1
  129. data/examples/msu_package/spec/remove_spec.rb +1 -1
  130. data/examples/multiple_actions/spec/default_spec.rb +1 -1
  131. data/examples/multiple_actions/spec/reversed_spec.rb +1 -1
  132. data/examples/multiple_actions/spec/sequential_spec.rb +1 -1
  133. data/examples/multiple_run_action/spec/default_spec.rb +1 -1
  134. data/examples/nothing_matcher/spec/default_spec.rb +1 -1
  135. data/examples/notifications/spec/before_spec.rb +1 -1
  136. data/examples/notifications/spec/chained_spec.rb +1 -1
  137. data/examples/notifications/spec/default_spec.rb +1 -1
  138. data/examples/notifications/spec/delayed_spec.rb +1 -1
  139. data/examples/notifications/spec/immediately_spec.rb +1 -1
  140. data/examples/ohai/spec/reload_spec.rb +1 -1
  141. data/examples/osx_profile/spec/install_spec.rb +1 -1
  142. data/examples/osx_profile/spec/remove_spec.rb +1 -1
  143. data/examples/package/spec/install_spec.rb +1 -1
  144. data/examples/package/spec/lock_spec.rb +1 -1
  145. data/examples/package/spec/purge_spec.rb +1 -1
  146. data/examples/package/spec/reconfig_spec.rb +1 -1
  147. data/examples/package/spec/remove_spec.rb +1 -1
  148. data/examples/package/spec/unlock_spec.rb +1 -1
  149. data/examples/package/spec/upgrade_spec.rb +1 -1
  150. data/examples/pacman_package/spec/install_spec.rb +1 -1
  151. data/examples/pacman_package/spec/purge_spec.rb +1 -1
  152. data/examples/pacman_package/spec/remove_spec.rb +1 -1
  153. data/examples/pacman_package/spec/upgrade_spec.rb +1 -1
  154. data/examples/paludis_package/spec/install_spec.rb +1 -1
  155. data/examples/paludis_package/spec/purge_spec.rb +1 -1
  156. data/examples/paludis_package/spec/remove_spec.rb +1 -1
  157. data/examples/paludis_package/spec/upgrade_spec.rb +1 -1
  158. data/examples/portage_package/spec/install_spec.rb +1 -1
  159. data/examples/portage_package/spec/purge_spec.rb +1 -1
  160. data/examples/portage_package/spec/remove_spec.rb +1 -1
  161. data/examples/portage_package/spec/upgrade_spec.rb +1 -1
  162. data/examples/powershell_script/spec/run_spec.rb +1 -1
  163. data/examples/reboot/spec/cancel_spec.rb +1 -1
  164. data/examples/reboot/spec/now_spec.rb +1 -1
  165. data/examples/reboot/spec/request_spec.rb +1 -1
  166. data/examples/recipe_block/metadata.rb +1 -0
  167. data/examples/recipe_block/spec/recipe_block_spec.rb +34 -0
  168. data/examples/recipe_block/templates/apache2.conf.erb +1 -0
  169. data/examples/registry_key/spec/create_if_missing_spec.rb +1 -1
  170. data/examples/registry_key/spec/create_spec.rb +1 -1
  171. data/examples/registry_key/spec/delete_key_spec.rb +1 -1
  172. data/examples/registry_key/spec/delete_spec.rb +1 -1
  173. data/examples/remote_directory/spec/create_if_missing_spec.rb +1 -1
  174. data/examples/remote_directory/spec/create_spec.rb +1 -1
  175. data/examples/remote_directory/spec/delete_spec.rb +1 -1
  176. data/examples/remote_file/spec/create_if_missing_spec.rb +1 -1
  177. data/examples/remote_file/spec/create_spec.rb +1 -1
  178. data/examples/remote_file/spec/delete_spec.rb +1 -1
  179. data/examples/remote_file/spec/touch_spec.rb +1 -1
  180. data/examples/render_file/spec/default_spec.rb +12 -1
  181. data/examples/render_file/spec/template_helpers_spec.rb +1 -1
  182. data/examples/roles/spec/default_spec.rb +1 -1
  183. data/examples/route/spec/add_spec.rb +1 -1
  184. data/examples/route/spec/delete_spec.rb +1 -1
  185. data/examples/rpm_package/spec/install_spec.rb +1 -1
  186. data/examples/rpm_package/spec/remove_spec.rb +1 -1
  187. data/examples/rpm_package/spec/upgrade_spec.rb +1 -1
  188. data/examples/ruby_block/spec/create_spec.rb +1 -1
  189. data/examples/ruby_block/spec/run_spec.rb +1 -1
  190. data/examples/runner/recipes/default.rb +1 -0
  191. data/examples/runner/spec/default_spec.rb +11 -0
  192. data/examples/script/spec/run_bash_spec.rb +1 -1
  193. data/examples/script/spec/run_csh_spec.rb +1 -1
  194. data/examples/script/spec/run_ksh_spec.rb +1 -1
  195. data/examples/script/spec/run_perl_spec.rb +1 -1
  196. data/examples/script/spec/run_python_spec.rb +1 -1
  197. data/examples/script/spec/run_ruby_spec.rb +1 -1
  198. data/examples/script/spec/run_script_spec.rb +1 -1
  199. data/examples/server/spec/client_spec.rb +1 -1
  200. data/examples/server/spec/data_bag_spec.rb +1 -1
  201. data/examples/server/spec/environment_spec.rb +1 -1
  202. data/examples/server/spec/exotic_port_spec.rb +1 -1
  203. data/examples/server/spec/node_spec.rb +4 -4
  204. data/examples/server/spec/render_with_cached_spec.rb +1 -1
  205. data/examples/server/spec/role_spec.rb +1 -1
  206. data/examples/server/spec/search_spec.rb +1 -1
  207. data/examples/service/spec/disable_spec.rb +1 -1
  208. data/examples/service/spec/enable_spec.rb +1 -1
  209. data/examples/service/spec/reload_spec.rb +1 -1
  210. data/examples/service/spec/restart_spec.rb +1 -1
  211. data/examples/service/spec/start_spec.rb +1 -1
  212. data/examples/service/spec/stop_spec.rb +1 -1
  213. data/examples/smartos_package/spec/install_spec.rb +1 -1
  214. data/examples/smartos_package/spec/remove_spec.rb +1 -1
  215. data/examples/smartos_package/spec/upgrade_spec.rb +1 -1
  216. data/examples/solaris_package/spec/install_spec.rb +1 -1
  217. data/examples/solaris_package/spec/remove_spec.rb +1 -1
  218. data/examples/solaris_package/spec/upgrade_spec.rb +1 -1
  219. data/examples/spec_attributes/attributes/default.rb +1 -0
  220. data/examples/spec_attributes/recipes/default.rb +5 -0
  221. data/examples/spec_attributes/spec/default_spec.rb +54 -0
  222. data/examples/spec_platform/recipes/default.rb +3 -0
  223. data/examples/spec_platform/spec/default_spec.rb +36 -0
  224. data/examples/spec_step_into/recipes/default.rb +3 -0
  225. data/examples/spec_step_into/resources/one.rb +3 -0
  226. data/examples/spec_step_into/resources/two.rb +3 -0
  227. data/examples/spec_step_into/spec/default_spec.rb +50 -0
  228. data/examples/state_attrs/spec/default_spec.rb +1 -1
  229. data/examples/step_into/spec/default_spec.rb +2 -2
  230. data/examples/stub_command/spec/default_spec.rb +1 -1
  231. data/examples/stub_data_bag/spec/default_spec.rb +1 -1
  232. data/examples/stub_data_bag_item/spec/default_spec.rb +1 -1
  233. data/examples/stub_node/spec/default_spec.rb +1 -1
  234. data/examples/stub_search/spec/block_spec.rb +1 -1
  235. data/examples/stub_search/spec/default_spec.rb +1 -1
  236. data/examples/stubs_for/resources/default.rb +20 -0
  237. data/examples/stubs_for/resources/old.rb +26 -0
  238. data/examples/stubs_for/spec/default_spec.rb +240 -0
  239. data/examples/subscribes/spec/before_spec.rb +1 -1
  240. data/examples/subscribes/spec/chained_spec.rb +1 -1
  241. data/examples/subscribes/spec/default_spec.rb +1 -1
  242. data/examples/subscribes/spec/delayed_spec.rb +1 -1
  243. data/examples/subscribes/spec/immediately_spec.rb +1 -1
  244. data/examples/subversion/spec/checkout_spec.rb +1 -1
  245. data/examples/subversion/spec/export_spec.rb +1 -1
  246. data/examples/subversion/spec/force_export_spec.rb +1 -1
  247. data/examples/subversion/spec/sync_spec.rb +1 -1
  248. data/examples/systemd_unit/spec/create_spec.rb +1 -1
  249. data/examples/systemd_unit/spec/delete_spec.rb +1 -1
  250. data/examples/systemd_unit/spec/disable_spec.rb +1 -1
  251. data/examples/systemd_unit/spec/enable_spec.rb +1 -1
  252. data/examples/systemd_unit/spec/mask_spec.rb +1 -1
  253. data/examples/systemd_unit/spec/reload_or_restart_spec.rb +1 -1
  254. data/examples/systemd_unit/spec/reload_or_try_restart_spec.rb +1 -1
  255. data/examples/systemd_unit/spec/restart_spec.rb +1 -1
  256. data/examples/systemd_unit/spec/start_spec.rb +1 -1
  257. data/examples/systemd_unit/spec/stop_spec.rb +1 -1
  258. data/examples/systemd_unit/spec/try_restart_spec.rb +1 -1
  259. data/examples/systemd_unit/spec/unmask_spec.rb +1 -1
  260. data/examples/template/spec/create_if_missing_spec.rb +1 -1
  261. data/examples/template/spec/create_spec.rb +1 -1
  262. data/examples/template/spec/delete_spec.rb +1 -1
  263. data/examples/template/spec/touch_spec.rb +1 -1
  264. data/examples/use_inline_resources/spec/default_spec.rb +1 -1
  265. data/examples/user/spec/create_spec.rb +1 -1
  266. data/examples/user/spec/lock_spec.rb +1 -1
  267. data/examples/user/spec/manage_spec.rb +1 -1
  268. data/examples/user/spec/modify_spec.rb +1 -1
  269. data/examples/user/spec/remove_spec.rb +1 -1
  270. data/examples/user/spec/unlock_spec.rb +1 -1
  271. data/examples/windows_package/spec/install_spec.rb +1 -1
  272. data/examples/windows_package/spec/remove_spec.rb +1 -1
  273. data/examples/windows_service/spec/configure_startup_spec.rb +1 -1
  274. data/examples/windows_service/spec/disable_spec.rb +1 -1
  275. data/examples/windows_service/spec/enable_spec.rb +1 -1
  276. data/examples/windows_service/spec/reload_spec.rb +1 -1
  277. data/examples/windows_service/spec/restart_spec.rb +1 -1
  278. data/examples/windows_service/spec/start_spec.rb +1 -1
  279. data/examples/windows_service/spec/stop_spec.rb +1 -1
  280. data/examples/yum_package/spec/install_spec.rb +1 -1
  281. data/examples/yum_package/spec/lock_spec.rb +1 -1
  282. data/examples/yum_package/spec/purge_spec.rb +1 -1
  283. data/examples/yum_package/spec/remove_spec.rb +1 -1
  284. data/examples/yum_package/spec/unlock_spec.rb +1 -1
  285. data/examples/yum_package/spec/upgrade_spec.rb +1 -1
  286. data/examples/yum_repository/spec/add_spec.rb +1 -1
  287. data/examples/yum_repository/spec/create_spec.rb +1 -1
  288. data/examples/yum_repository/spec/delete_spec.rb +1 -1
  289. data/examples/yum_repository/spec/makecache_spec.rb +1 -1
  290. data/examples/yum_repository/spec/remove_spec.rb +1 -1
  291. data/examples/zypper_package/spec/install_spec.rb +1 -1
  292. data/examples/zypper_package/spec/lock_spec.rb +1 -1
  293. data/examples/zypper_package/spec/purge_spec.rb +1 -1
  294. data/examples/zypper_package/spec/remove_spec.rb +1 -1
  295. data/examples/zypper_package/spec/unlock_spec.rb +1 -1
  296. data/examples/zypper_package/spec/upgrade_spec.rb +1 -1
  297. data/lib/chefspec.rb +1 -1
  298. data/lib/chefspec/api.rb +33 -14
  299. data/lib/chefspec/api/core.rb +195 -0
  300. data/lib/chefspec/api/described.rb +55 -0
  301. data/lib/chefspec/api/do_nothing.rb +24 -19
  302. data/lib/chefspec/api/include_recipe.rb +26 -21
  303. data/lib/chefspec/api/link.rb +26 -21
  304. data/lib/chefspec/api/notifications.rb +38 -33
  305. data/lib/chefspec/api/reboot.rb +11 -6
  306. data/lib/chefspec/api/render_file.rb +35 -30
  307. data/lib/chefspec/api/state_attrs.rb +28 -23
  308. data/lib/chefspec/api/stubs.rb +183 -0
  309. data/lib/chefspec/api/stubs_for.rb +134 -0
  310. data/lib/chefspec/api/subscriptions.rb +35 -30
  311. data/lib/chefspec/api/user.rb +222 -217
  312. data/lib/chefspec/berkshelf.rb +1 -1
  313. data/lib/chefspec/deprecations.rb +2 -11
  314. data/lib/chefspec/errors.rb +1 -0
  315. data/lib/chefspec/extensions.rb +4 -1
  316. data/lib/chefspec/extensions/chef/client.rb +3 -4
  317. data/lib/chefspec/extensions/chef/conditional.rb +3 -2
  318. data/lib/chefspec/extensions/chef/cookbook/gem_installer.rb +25 -28
  319. data/lib/chefspec/extensions/chef/cookbook_loader.rb +13 -0
  320. data/lib/chefspec/extensions/chef/cookbook_uploader.rb +3 -2
  321. data/lib/chefspec/extensions/chef/data_query.rb +5 -8
  322. data/lib/chefspec/extensions/chef/lwrp_base.rb +17 -21
  323. data/lib/chefspec/extensions/chef/provider.rb +36 -0
  324. data/lib/chefspec/extensions/chef/resource.rb +51 -4
  325. data/lib/chefspec/extensions/chef/resource/freebsd_package.rb +13 -16
  326. data/lib/chefspec/extensions/chef/run_context/cookbook_compiler.rb +64 -0
  327. data/lib/chefspec/matchers/render_file_matcher.rb +36 -26
  328. data/lib/chefspec/matchers/resource_matcher.rb +1 -1
  329. data/lib/chefspec/rspec.rb +3 -8
  330. data/lib/chefspec/runner.rb +8 -0
  331. data/lib/chefspec/server_methods.rb +1 -1
  332. data/lib/chefspec/server_runner.rb +13 -9
  333. data/lib/chefspec/solo_runner.rb +136 -32
  334. data/lib/chefspec/version.rb +1 -1
  335. data/spec/unit/deprecations_spec.rb +1 -26
  336. data/spec/unit/macros_spec.rb +14 -14
  337. data/spec/unit/matchers/render_file_matcher_spec.rb +8 -1
  338. data/spec/unit/solo_runner_spec.rb +3 -3
  339. data/templates/errors/may_need_to_specify_platform.erb +1 -1
  340. data/templates/errors/shell_out_not_stubbed.erb +10 -0
  341. metadata +38 -4
  342. data/lib/chefspec/macros.rb +0 -222
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27b7c297c627d6ca7e5c5115aa75d544879ecca8e9d601784aad2a8834a367c4
4
- data.tar.gz: 293c807e58f9f61bb73aacbe0a86fede3833adc3bc79fc255b8dab4dbe9fd945
3
+ metadata.gz: 4149e5d01cef76e9961206a3c21e026f8d1514c5411d4e736775494e7df2fd5e
4
+ data.tar.gz: 1c2f041d3734fb0d1b4311652e545753ae93e13ac1d5699287754a2005ab30c5
5
5
  SHA512:
6
- metadata.gz: cb27b20b64cc932b1b543d9a93f49dec7dfc4c101b3fe6b57a2132e1d0366cfc4f9f8bcfb189332dc3a1280792d755499c836f38cb78ddb9fe003eb0eedb6ab8
7
- data.tar.gz: b7bacaf0f808fae388fa2eb061524645f2d6d3077fc715e519626ad74f5184b6d4382dd32cf5d5abbb74a9bd8d409e4382f90a1196ec4b068da2e1295cb6e4a0
6
+ metadata.gz: 86b9445a05713b120154104b065300b29944c2178277c0dbaf57271baed0d675b9582272c73044101cc36625b7ff9e1d189efff6f8525dbac2aed5ba225be0fd
7
+ data.tar.gz: 6dc383647ef0ca7f97c3a4ecfbe68a4323949a95d03c8ee853c80000682a7eab9deebed4db231cedd7ba0765c37d18a3304e145ed012739b6a5283b927f43889
@@ -16,25 +16,33 @@ bundler_args: --jobs 7 --retry 3
16
16
 
17
17
  matrix:
18
18
  include:
19
- - env: CHEF_VERSION=master
20
- rvm: 2.5.0
21
- - env: CHEF_VERSION=13.8.3
19
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 14.3.37'\""
20
+ rvm: 2.5.1
21
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 14.2.0'\""
22
+ rvm: 2.5.1
23
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 14.1.12'\""
24
+ rvm: 2.5.1
25
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 14.0.202'\""
26
+ rvm: 2.5.1
27
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 13.10.0'\""
28
+ rvm: 2.4.4
29
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 13.9.4'\""
30
+ rvm: 2.4.4
31
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 13.8.5'\""
22
32
  rvm: 2.4.3
23
- - env: CHEF_VERSION=13.7.16
33
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 13.7.16'\""
24
34
  rvm: 2.4.3
25
- - env: CHEF_VERSION=13.6.4
35
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 13.6.4'\""
26
36
  rvm: 2.4.2
27
- - env: CHEF_VERSION=13.5.3
37
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 13.5.3'\""
28
38
  rvm: 2.4.2
29
- - env: CHEF_VERSION=13.4.24
39
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 13.4.24'\""
30
40
  rvm: 2.4.2
31
- - env: CHEF_VERSION=13.3.42
41
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 13.3.42'\""
32
42
  rvm: 2.4.2
33
- - env: CHEF_VERSION=13.2.20
43
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 13.2.20'\""
34
44
  rvm: 2.4.2
35
- - env: CHEF_VERSION=13.1.31
45
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 13.1.31'\""
36
46
  rvm: 2.4.2
37
- - env: CHEF_VERSION=13.0.118
47
+ - env: "GEMFILE_MOD=\"gem 'chef', '= 13.0.118'\""
38
48
  rvm: 2.4.2
39
- - env: CHEF_VERSION=12.21.31
40
- rvm: 2.3.5
@@ -1,5 +1,9 @@
1
1
  # CHANGELOG for ChefSpec
2
2
 
3
+ ## 7.3.0 (August 30, 2018)
4
+
5
+ - Major syntax overhaul and update. Check out the [README](/README.md) for examples of the updated syntax. The older syntax is still present and will be supported at least until ChefSpec 8.0, but documentation has been moved to [README_old](/README_old.md).
6
+
3
7
  ## 7.2.1 (May, 8, 2018)
4
8
 
5
9
  - Updated an example and test for that example
data/Gemfile CHANGED
@@ -1,19 +1,23 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # env var for travis
4
- if ENV['CHEF_VERSION']
5
- if ENV['CHEF_VERSION'] == "master"
6
- gem 'chef', git: "https://github.com/chef/chef"
7
- gem 'ohai', git: "https://github.com/chef/ohai"
8
- else
9
- gem 'chef', ENV['CHEF_VERSION']
10
- end
11
- end
12
-
13
3
  gemspec
14
4
 
15
5
  group :development do
16
6
  gem 'rake'
17
7
  gem 'redcarpet'
18
8
  gem 'yard'
9
+ gem 'pry'
10
+ gem 'pry-byebug'
19
11
  end
12
+
13
+ if ENV["GEMFILE_MOD"]
14
+ puts "GEMFILE_MOD: #{ENV['GEMFILE_MOD']}"
15
+ instance_eval(ENV["GEMFILE_MOD"])
16
+ else
17
+ gem 'chef', git: "https://github.com/chef/chef"
18
+ gem 'ohai', git: "https://github.com/chef/ohai"
19
+ end
20
+
21
+ # If you want to load debugging tools into the bundle exec sandbox,
22
+ # add these additional dependencies into Gemfile.local
23
+ eval_gemfile(__FILE__ + ".local") if File.exist?(__FILE__ + ".local")
data/README.md CHANGED
@@ -4,1241 +4,538 @@
4
4
 
5
5
  ChefSpec is a unit testing framework for testing Chef cookbooks. ChefSpec makes it easy to write examples and get fast feedback on cookbook changes without the need for virtual machines or cloud servers.
6
6
 
7
- ChefSpec runs your cookbook(s) locally with Chef Solo without actually converging a node. This has two primary benefits:
7
+ ChefSpec runs your cookbooks locally while skipping making actual changes. This has two primary benefits:
8
8
 
9
9
  - It's really fast!
10
- - Your tests can vary node attributes, operating systems, and search results to assert behavior under varying conditions.
10
+ - Your tests can vary node attributes, operating systems, and other system data to assert behavior under varying conditions.
11
11
 
12
12
  ## Important Notes
13
13
 
14
14
  - **ChefSpec requires Ruby 2.2 or later and Chef 12.14.89 or later!**
15
15
  - **This documentation corresponds to the master branch, which may be unreleased. Please check the README of the latest git tag or the gem's source for your version's documentation!**
16
- - **Each resource matcher is self-documented using [Yard](http://rubydoc.info/github/chefspec/chefspec) and has a corresponding test recipe in the [examples directory](https://github.com/chefspec/chefspec/tree/master/examples).**
17
16
 
18
- ## Notes on Compatibility with Chef Versions
17
+ **ChefSpec aims to maintain compatibility with at least the two most recent minor versions of Chef.** If you are running an older version of Chef it may work, or you will need to run an older version of ChefSpec.
19
18
 
20
- **ChefSpec aims to maintain compatibility with the two most recent minor versions of Chef.** If you are running an older version of Chef it may work, or you will need to run an older version of ChefSpec.
19
+ As a general rule, if it is tested in the Travis CI matrix, it is a supported version.
21
20
 
22
- As a general rule, if it is tested in the Travis CI matrix, it is a supported version. The section below details any specific versions that are _not_ supported and why:
21
+ ## Quick Start
23
22
 
24
- ## Writing a Cookbook Example
23
+ ## When To Use ChefSpec?
25
24
 
26
- If you want `knife` to automatically generate spec stubs for you, install [knife-spec](https://github.com/sethvargo/knife-spec).
25
+ As mentioned before, ChefSpec is built for speed. In order to run your tests as
26
+ quickly as possible (and to allow running tests on your workstation), ChefSpec
27
+ runs your recipe code with all the resource actions disabled. This means that
28
+ ChefSpec excels at testing complex logic in a cookbook, but can't actually tell
29
+ you if a cookbook is doing the right thing. Integration testing is provided by
30
+ the [Test Kitchen](https://kitchen.ci/) project, and for most simple cookbooks
31
+ without much logic in them we recommend you start off with integration tests and
32
+ only return to ChefSpec and unit tests as your code gets more complicated.
27
33
 
28
- Given an extremely basic Chef recipe that just installs an operating system package:
34
+ There are two common "units" of code in Chef cookbooks, custom resources and
35
+ recipes. If you find yourself with a lot of recipes that are so complex they
36
+ require unit tests, consider if they can be refactored as custom resources.
29
37
 
30
- ```ruby
31
- package 'foo'
32
- ```
38
+ ### Testing a Custom Resource
33
39
 
34
- the associated ChefSpec test might look like:
40
+ If you have have a cookbook with a custom resource `resources/greet.rb` like:
35
41
 
36
42
  ```ruby
37
- require 'chefspec'
43
+ resource_name :mycookbook_greet
38
44
 
39
- describe 'example::default' do
40
- let(:chef_run) { ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '16.04').converge(described_recipe) }
45
+ property :greeting, String, default: 'Hello'
41
46
 
42
- it 'installs foo' do
43
- expect(chef_run).to install_package('foo')
44
- end
47
+ action :run do
48
+ log "#{new_resource.greeting} world"
45
49
  end
46
50
  ```
47
51
 
48
- Let's step through this file to see what is happening:
49
-
50
- 1. At the top of the spec file we require the chefspec gem. This is required so that our custom matchers are loaded. In larger projects, it is common practice to create a file named "spec_helper.rb" and include ChefSpec and perform other setup tasks in that file.
51
- 2. The `describe` keyword is part of RSpec and indicates that everything nested beneath is describing the `example::default` recipe. The convention is to have a separate spec for each recipe in your cookbook.
52
- 3. The `let` block on creates the `ChefSpec:SoloRunner` with mocked Ohai data for Ubuntu 16.04 from [Fauxhai](https://github.com/chefspec/fauxhai). It then does a fake Chef run with the run_list of `example::default`. Any subsequent examples can then refer to `chef_run` in order to make assertions about the resources that were created during the mock converge.
53
- 4. The `described_recipe` macro is a ChefSpec helper method that infers the recipe from the `describe` block. Alternatively you could specify the recipe directly.
54
- 5. The `it` block is an example specifying that the `foo` package is installed. Normally you will have multiple `it` blocks per recipe, each making a single assertion.
55
-
56
- ## Configuration
57
-
58
- ChefSpec exposes a configuration layer at the global level and at the `Runner` level. The following settings are available:
52
+ You can test that resource by creating a spec file `spec/greet_spec.rb`:
59
53
 
60
54
  ```ruby
61
- RSpec.configure do |config|
62
- # Specify the path for Chef Solo to find cookbooks (default: [inferred from
63
- # the location of the calling spec file])
64
- config.cookbook_path = '/var/cookbooks'
65
-
66
- # Specify the path for Chef Solo to find roles (default: [ascending search])
67
- config.role_path = '/var/roles'
68
-
69
- # Specify the path for Chef Solo to find environments (default: [ascending search])
70
- config.environment_path = '/var/environments'
71
-
72
- # Specify the path for Chef Solo file cache path (default: nil)
73
- config.file_cache_path = Chef::Config[:file_cache_path]
55
+ # Load ChefSpec and put our test into ChefSpec mode.
56
+ require 'chefspec'
74
57
 
75
- # Specify the Chef log_level (default: :warn)
76
- config.log_level = :debug
58
+ # Describing our custom resource.
59
+ describe 'mycookbook_greet' do
60
+ # Normally ChefSpec skips running resources, but for this test we want to
61
+ # actually run this one custom resource.
62
+ step_into :mycookbook_greet
63
+ # Nothing in this test is platform-specific, so use the latest Ubuntu for
64
+ # simulated data.
65
+ platform 'ubuntu'
66
+
67
+ # Create an example group for testing the resource defaults.
68
+ context 'with the default greeting' do
69
+ # Set the subject of this example group to a snippet of recipe code calling
70
+ # our custom resource.
71
+ recipe do
72
+ mycookbook_greet 'test'
73
+ end
77
74
 
78
- # Specify the path to a local JSON file with Ohai data (default: nil)
79
- config.path = 'ohai.json'
75
+ # Confirm that the resources created by our custom resource's action are
76
+ # correct. ChefSpec matchers all take the form `action_type(name)`.
77
+ it { is_expected.to write_log('Hello world') }
78
+ end
80
79
 
81
- # Specify the operating platform to mock Ohai data from (default: nil)
82
- config.platform = 'ubuntu'
80
+ # Create a second example group to test a different block of recipe code.
81
+ context 'with a custom greeting' do
82
+ # This time our test recipe code sets a property on the custom resource.
83
+ recipe do
84
+ mycookbook_greet 'test' do
85
+ greeting 'Bonjour'
86
+ end
87
+ end
83
88
 
84
- # Specify the operating version to mock Ohai data from (default: nil)
85
- config.version = '14.04'
89
+ # Use the same kind of matcher as before to confirm the action worked.
90
+ it { is_expected.to write_log('Bonjour world') }
91
+ end
86
92
  end
87
93
  ```
88
94
 
89
- Values specified at the initialization of a "Runner" merge and take precedence over any global settings:
90
-
91
- ```ruby
92
- # Override only the operating system version (platform is still "ubuntu" from above)
93
- ChefSpec::SoloRunner.new(version: '16.04')
94
-
95
- # Use a different operating system platform and version
96
- ChefSpec::SoloRunner.new(platform: 'centos', version: '7.3.1611')
97
-
98
- # Specify a different cookbook_path
99
- ChefSpec::SoloRunner.new(cookbook_path: '/var/my/other/path', role_path: '/var/my/roles')
100
-
101
- # By default ChefSpec sets a new temporary directory for file caching in every run.
102
- # This can be overridden by passing the `file_cache_path` option.
103
- # Note: Resources containing `Chef::Config[:file_cache_path]` in their name or
104
- # attributes, will fail unless this option is specified.
105
- ChefSpec::SoloRunner.new(file_cache_path: Chef::Config[:file_cache_path])
106
-
107
- # Add debug log output
108
- ChefSpec::SoloRunner.new(log_level: :debug).converge(described_recipe)
109
- ```
110
-
111
- **NOTE** You do not _need_ to specify a platform and version to use ChefSpec. However, some cookbooks may rely on [Ohai](http://github.com/chef/ohai) data that ChefSpec cannot not automatically generate. Specifying the `platform` and `version` keys instructs ChefSpec to load stubbed Ohai attributes from another platform using [fauxhai](https://github.com/chefspec/fauxhai). See the [PLATFORMS.md file](https://github.com/chefspec/fauxhai/blob/master/PLATFORMS.md) in the Fauxhai repo for a complete list of platforms and versions for use with ChefSpec.
95
+ And then run your test using `chef exec rspec`.
112
96
 
113
- ### ChefZero Server
97
+ ### Testing a Recipe
114
98
 
115
- The `ServerRunner` uses a [chef-zero](https://github.com/chef/chef-zero) instance as a stand-in for a full Chef Server. The instance is created at the initiation of the ChefSpec suite and is terminated at its completion. In between each test the state of the ChefZero server is completely reset.
99
+ As a general rule of thumb, only very complex recipes benefit from ChefSpec unit
100
+ tests. If you find yourself writing a lot of recipe unit tests, consider converting
101
+ the recipes to custom resources instead. For the sake of example we'll use a
102
+ simple recipe, `recipes/farewell.rb`:
116
103
 
117
104
  ```ruby
118
- RSpec.configure do |config|
119
- # When using ChefSpec::ServerRunner, specify the data storage method (options: in_memory, on_disk; default: in_memory)
120
- # If you are in a low-memory environment, setting this value to :on_disk may improve speed and/or reliability.
121
- config.server_runner_data_store = :on_disk
122
-
123
- # Whether or not to clear the cookbooks on the ChefZero instance in-between each test (default: true)
124
- # For most people, not clearing the cookbooks will drastically improve test execution time. This is a
125
- # good option for people who are using chefspec within the context of a single Berksfile or Policyfile.
126
- config.server_runner_clear_cookbooks = false
127
- end
105
+ log "#{node["mycookbook"]["farewell"]} world"
128
106
  ```
129
107
 
130
- ### Berkshelf
131
-
132
- If you are using Berkshelf, simply require `chefspec/berkshelf` in your `spec_helper` after requiring `chefspec`:
108
+ You can test that recipe by creating a spec file `spec/farewell_spec.rb`:
133
109
 
134
110
  ```ruby
135
- # spec_helper.rb
111
+ # Load ChefSpec and put our test into ChefSpec mode.
136
112
  require 'chefspec'
137
- require 'chefspec/berkshelf'
138
- ```
139
-
140
- Requiring this file will:
141
113
 
142
- - Create a temporary working directory
143
- - Download all the dependencies listed in your `Berksfile` into the temporary directory
144
- - Set ChefSpec's `cookbook_path` to the temporary directory
114
+ # Describing our recipe. The group name should be the recipe string as you would
115
+ # use it with include_recipe.
116
+ describe 'mycookbook::farewell' do
117
+ # Nothing in this test is platform-specific, so use the latest Ubuntu for
118
+ # simulated data.
119
+ platform 'ubuntu'
120
+
121
+ # Create an example group for testing the recipe defaults.
122
+ context 'with default attributes' do
123
+ # Since there was no `recipe do .. end` block here, the default subject is
124
+ # recipe we named in the `describe`. ChefSpec matchers all take the form
125
+ # `action_type(name)`.
126
+ it { is_expected.to write_log('Goodbye world') }
127
+ end
145
128
 
146
- You can customize the list of options passed to the installation command using the `berkshelf_options` RSpec configuration:
129
+ # Create a second example group to test with attributes.
130
+ context 'with a custom farewell' do
131
+ # Set an override attribute for this group.
132
+ override_attributes['mycookbook']['farewell'] = 'Adios'
147
133
 
148
- ```ruby
149
- RSpec.configuration do |config|
150
- config.berkshelf_options = { only: "my-group" }
134
+ # Use the same kind of matcher as before to confirm the recipe worked.
135
+ it { is_expected.to write_log('Adios world') }
136
+ end
151
137
  end
152
138
  ```
153
139
 
154
- This is a Ruby hash and valid options include `only` and `except`.
140
+ ## Cookbook Dependencies
155
141
 
156
- ### Librarian
142
+ If your cookbook depends on other cookbooks, you must ensure ChefSpec knows how
143
+ to fetch those dependencies. If you use a monorepo-style layout with all your
144
+ cookbooks in a single `cookbooks/` folder, you don't need to do anything.
157
145
 
158
- If you are using Librarian, simply require `chefspec/librarian` in your `spec_helper` after requiring `chefspec`:
146
+ If you are using Berkshelf, `require 'chefspec/berkshelf'` in your spec file (or `spec_helper.rb`):
159
147
 
160
148
  ```ruby
161
- # spec_helper.rb
162
149
  require 'chefspec'
163
- require 'chefspec/librarian'
164
- ```
165
-
166
- Requiring this file will:
167
-
168
- - Create a temporary working directory
169
- - Download all the dependencies listed in your `Cheffile` into the temporary directory
170
- - Set ChefSpec's `cookbook_path` to the temporary directory
171
-
172
- **NOTE** In order to test the cookbook in the current working directory, you have to write your `Cheffile` like this:
173
-
174
- ```ruby
175
- # Cheffile
176
- site 'https://supermarket.chef.io/api/v1'
177
-
178
- cookbook 'name_of_your_cookbook', path: '.'
150
+ require 'chefspec/berkshelf'
179
151
  ```
180
152
 
181
- ### Policyfile
182
-
183
- If you are using Chef Policies with ChefDK, simply require `chefspec/policyfile` in your `spec_helper`, and ensure you are using the `ChefSpec::ServerRunner` - Chef Solo does not support the exported repository format because the cookbook names use the unique version identifier.
153
+ If you are using a Policyfile, `require 'chefspec/policyfile'` in you spec file (or `spec_helper.rb`):
184
154
 
185
155
  ```ruby
186
- # spec_helper.rb
187
156
  require 'chefspec'
188
157
  require 'chefspec/policyfile'
189
158
  ```
190
159
 
191
- Requiring this file will:
192
-
193
- - Create a temporary working directory
194
- - Download all the dependencies listed in your `Policyfile.rb` into the temporary directory
195
- - Set ChefSpec's `cookbook_path` to the temporary directory
196
-
197
160
  Your `Policyfile.rb` should look something like this:
198
161
 
199
162
  ```ruby
200
- name 'my-cookbook'
201
- run_list 'my-cookbook::default'
202
- default_source :community
203
- cookbook 'my-cookbook', path: '.'
163
+ # The policy name is ignored but you need to specify one.
164
+ name 'my_cookbook'
165
+ # Pull dependent cookbooks from https://supermarket.chef.io/
166
+ default_source :supermarket
167
+ # The run list is also ignored by ChefSpec but you need to specify one.
168
+ run_list 'my_cookbook::default'
169
+ # The name here must match the name in metadata.rb.
170
+ cookbook 'my_cookbook', path: '.'
204
171
  ```
205
172
 
206
- ## Running Specs
173
+ ## Writing Tests
207
174
 
208
- ChefSpec is actually an RSpec extension, so you can run your tests using the RSpec CLI:
209
-
210
- ```bash
211
- $ rspec
212
- ```
213
-
214
- You can also specify a specific spec to run and various RSpec command line options:
215
-
216
- ```bash
217
- $ rspec spec/unit/recipes/default_spec.rb --color
218
- ```
219
-
220
- For more information on the RSpec CLI, please see the [documentation](https://relishapp.com/rspec/rspec-core/docs/command-line).
221
-
222
- ## Making Assertions
223
-
224
- ChefSpec asserts that resource actions have been performed. In general, ChefSpec follows the following pattern:
175
+ ChefSpec is an RSpec library, so if you're already familiar with RSpec you can
176
+ use all the normal spec-y goodness to which you are accustomed. The usual structure
177
+ of an RSpec test file is a file named like `spec/something_spec.rb` containing:
225
178
 
226
179
  ```ruby
227
180
  require 'chefspec'
228
181
 
229
- describe 'example::default' do
230
- let(:chef_run) { ChefSpec::SoloRunner.converge(described_recipe) }
231
-
232
- it 'does something' do
233
- expect(chef_run).to ACTION_RESOURCE(NAME)
182
+ describe 'resource name or recipe' do
183
+ # Some configuration for everything inside this `describe`.
184
+ platform 'redhat', '7'
185
+ default_attributes['value'] = 1
186
+
187
+ context 'when some condition' do
188
+ # Some configuration that only applies to this `context`.
189
+ default_attributes['value'] = 2
190
+
191
+ # `matcher` is some matcher function which we'll cover below.
192
+ it { expect(value).to matcher }
193
+ # There is a special value you can expect things on called `subject`, which
194
+ # is the main thing being tested.
195
+ it { expect(subject).to matcher }
196
+ # And if prefer it for readability, `expect(subject)` can be written as `is_expected`.
197
+ it { is_expected.to matcher }
234
198
  end
235
- end
236
- ```
237
-
238
- where:
239
-
240
- - _ACTION_ - the action on the resource (e.g. `install`)
241
- - _RESOURCE_ - the name of the resource (e.g. `package`)
242
- - _NAME_ - the name attribute for the resource (e.g. `apache2`)
243
-
244
- **NOTE** One exception to this rule is the `create_if_missing` action on the `file` resource. In this case the assertion is actually `create_file_if_missing`. Refer to `examples/file/spec/create_if_missing_spec.rb` for some examples.
245
199
 
246
- Here's a more concrete example:
247
-
248
- ```ruby
249
- require 'chefspec'
250
-
251
- describe 'example::default' do
252
- let(:chef_run) { ChefSpec::SoloRunner.converge(described_recipe) }
253
-
254
- it 'installs apache2' do
255
- expect(chef_run).to install_package('apache2')
256
- end
257
- end
258
- ```
259
-
260
- This test is asserting that the Chef run will have a _package_ resource with the name _apache2_ with an action of _install_.
261
-
262
- To test that a resource action is performed with a specific set of attributes, you can call `with(ATTRIBUTES_HASH)` on the expectation, per the following example:
263
-
264
- ```ruby
265
- require 'chefspec'
266
-
267
- describe 'example::default' do
268
- let(:chef_run) { ChefSpec::SoloRunner.converge(described_recipe) }
269
-
270
- it 'adds the member vagrant to the docker group' do
271
- expect(chef_run).to modify_group('docker').with(members: ['vagrant'])
272
- end
273
- end
274
- ```
275
-
276
- This test is asserting that the Chef run will have a _group_ resource with the name _docker_, an action of _modify_, and an attributes hash including `{ members: ['vagrant'] }`.
277
-
278
- ChefSpec includes matchers for all of Chef's core resources using the above schema. Each resource matcher is self-documented using [Yard](http://rubydoc.info/github/chefspec/chefspec) and has a corresponding cucumber test from the [examples directory](https://github.com/chefspec/chefspec/tree/master/examples).
279
-
280
- Additionally, ChefSpec includes the following helpful matchers. They are also [documented in Yard](http://rubydoc.info/github/chefspec/chefspec), but they are included here because they do not follow the "general pattern".
281
-
282
- ### include_recipe
283
-
284
- Assert that the Chef run included a recipe from another cookbook
285
-
286
- ```ruby
287
- expect(chef_run).to include_recipe('other_cookbook::recipe')
288
- ```
289
-
290
- Keep the resources from an included recipe from being loaded into the Chef run, but test that the recipe was included
291
-
292
- ```ruby
293
- describe 'example::default' do
294
- let(:chef_run) { ChefSpec::SoloRunner.converge(described_recipe) }
295
-
296
- before do
297
- allow_any_instance_of(Chef::Recipe).to receive(:include_recipe).and_call_original
298
- allow_any_instance_of(Chef::Recipe).to receive(:include_recipe).with('other_cookbook::default')
299
- end
300
-
301
- it 'includes the other_cookbook' do
302
- expect_any_instance_of(Chef::Recipe).to receive(:include_recipe).with('other_cookbook::default')
303
- chef_run
200
+ context 'when some other condition' do
201
+ # Repeat as needed.
304
202
  end
305
203
  end
306
204
  ```
307
205
 
308
- ### notify
309
-
310
- Assert that a resource notifies another in the Chef run
311
-
312
- ```ruby
313
- resource = chef_run.template('/etc/foo')
314
- expect(resource).to notify('service[apache2]').to(:restart).immediately
315
- ```
316
-
317
- ### subscribes
206
+ ### ChefSpec Matchers
318
207
 
319
- Assert that a resource subscribes to another in the Chef run
208
+ The primary matcher used with ChefSpec are resource matchers:
320
209
 
321
210
  ```ruby
322
- resource = chef_run.service('apache2')
323
- expect(resource).to subscribe_to('template[/etc/foo]').on(:create).delayed
211
+ it { expect(chef_run).to ACTION_RESOURCE('NAME') }
212
+ # Or equivalently.
213
+ it { is_expected.to ACTION_RESOURCE('NAME') }
324
214
  ```
325
215
 
326
- ### render_file
327
-
328
- Assert that the Chef run renders a file (with optional content); this will match `cookbook_file`, `file`, and `template` resources and can also check the resulting content
216
+ This checks that a resource like `RESOURCE 'NAME'` would have run the specified
217
+ action if the cookbook was executing normally. You can also test for specific
218
+ property values:
329
219
 
330
220
  ```ruby
331
- expect(chef_run).to render_file('/etc/foo')
332
- expect(chef_run).to render_file('/etc/foo').with_content('This is content')
333
- expect(chef_run).to render_file('/etc/foo').with_content(/regex works too.+/)
334
- expect(chef_run).to render_file('/etc/foo').with_content { |content|
335
- # Regular RSpec matches work in here
336
- expect(content).to include('any RSpec matcher')
337
- }
221
+ it { is_expected.to create_user('asmithee').with(uid: 512, gid: 45) }
222
+ # You can also use other RSpec matchers to create a "compound matcher". Check
223
+ # RSpec documentation for a full reference on the built-in matchers.
224
+ it { is_expected.to install_package('myapp').with(version: starts_with("3.")) }
338
225
  ```
339
226
 
340
- You can use any RSpec content matcher inside of the `with_content` predicate:
341
-
342
- ```ruby
343
- expect(chef_run).to render_file('/etc/foo').with_content(start_with('# First line'))
344
- ```
227
+ #### `render_file`
345
228
 
346
- It is possible to assert which [Chef phase of execution](https://docs.chef.io/chef_client.html#the-chef-client-title-run) a resource is created. Given a resource that is installed at compile time using `run_action`:
229
+ For the common case of testing that a file is rendered to disk via either a
230
+ `template`, `file`, or `cookbook_file` resource, you can use a `render_file`
231
+ matcher:
347
232
 
348
233
  ```ruby
349
- package('apache2').run_action(:install)
234
+ it { is_expected.to render_file('/etc/myapp.conf') }
235
+ # You can check for specific content in the file.
236
+ it { is_expected.to render_file('/etc/myapp.conf').with_content("debug = false\n") }
237
+ # Or with a regexp.
238
+ it { is_expected.to render_file('/etc/myapp.conf').with_content(/user = \d+/) }
239
+ # Or with a compound matcher.
240
+ it { is_expected.to render_file('/etc/myapp.conf').with_content(start_with('# This file managed by Chef')) }
241
+ # Or with a Ruby block of arbitrary assertions.
242
+ it do
243
+ is_expected.to render_file('/etc/myapp.conf').with_content { |content|
244
+ # Arbitrary RSpec code goes here.
245
+ }
246
+ end
350
247
  ```
351
248
 
352
- You can assert that this package is installed during runtime using the `.at_compile_time` predicate on the resource matcher:
249
+ #### Notifications
353
250
 
354
- ```ruby
355
- expect(chef_run).to install_package('apache2').at_compile_time
356
- ```
357
-
358
- Similarly, you can assert that a resource is executed during convergence time:
251
+ As actions do not normally run in ChefSpec, testing for notifications is a special
252
+ case. Unlike the resource matchers which evaluate against the ChefSpec runner,
253
+ the notification matchers evaluate against a resource object:
359
254
 
360
255
  ```ruby
361
- expect(chef_run).to install_package('apache2').at_converge_time
256
+ # To match `notifies :run, 'execute[unpack]', :immediately
257
+ it { expect(chef_run.remote_file('/download')).to notify('execute[unpack]') }
258
+ # To check for a specific notification action.
259
+ it { expect(chef_run.remote_file('/download')).to notify('execute[unpack]').to(:run) }
260
+ # And to check for a specific timing.
261
+ it { expect(chef_run.remote_file('/download')).to notify('execute[unpack]').to(:run).immediately }
362
262
  ```
363
263
 
364
- Since "converge time" is the default behavior for all recipes, this test might be redundant and the predicate could be dropped depending on your situation.
365
-
366
- ### do_nothing
367
-
368
- Assert that a resource performs no action
264
+ And similarly for subscriptions:
369
265
 
370
266
  ```ruby
371
- resource = chef_run.execute('install')
372
- expect(resource).to do_nothing
267
+ it { expect(chef_run.execute('unpack')).to subscribe_to('remote_file[/download]').on(:create) }
373
268
  ```
374
269
 
375
- **For more complex examples, please see the [examples directory](https://github.com/chefspec/chefspec/tree/master/examples) or the [Yard documentation](http://rubydoc.info/github/chefspec/chefspec).**
376
-
377
- ## Specifying Node Information
378
-
379
- Node information can be set when creating the `Runner`. The initializer yields a block that gives full access to the node object:
380
-
381
- ```ruby
382
- describe 'example::default' do
383
- let(:chef_run) do
384
- ChefSpec::SoloRunner.new do |node|
385
- node.name 'test.node-1'
386
- end.converge(described_recipe)
387
- end
388
-
389
- it 'has node name set' do
390
- expect(chef_run.node.name).to eq('test.node-1')
391
- end
392
- end
393
- ```
270
+ ### Test Subject
394
271
 
395
- ### Setting Node Attributes
272
+ RSpec expectations always need a value to run against, with the main value being
273
+ tested for a given example group (`describe` or `context` block) is called the
274
+ `subject`. In ChefSpec this is almost always `ChefSpec::Runner` that has converge
275
+ some recipe code.
396
276
 
397
- Node attributes can also be set inside the initializer block:
277
+ There are two ways to set which recipe code should be run for the test. More commonly
278
+ for testing custom resources, you use the `recipe` helper method in the test to
279
+ provide an in-line block of recipe code:
398
280
 
399
281
  ```ruby
400
- describe 'example::default' do
401
- let(:chef_run) do
402
- ChefSpec::SoloRunner.new do |node|
403
- node.normal['cookbook']['attribute'] = 'hello'
404
- end.converge(described_recipe)
282
+ describe 'something' do
283
+ recipe do
284
+ my_custom_resource 'something' do
285
+ debug true
286
+ end
405
287
  end
406
288
  end
407
289
  ```
408
290
 
409
- ### Automatic attributes
291
+ By using an in-line block of recipe code, you can try many variations to test
292
+ different configurations of your custom resource.
410
293
 
411
- ChefSpec provides mocked automatic Ohai data using [fauxhai](https://github.com/chefspec/fauxhai). To mock out `automatic` attributes, you must use the `automatic` key:
294
+ If no `recipe` block is present, ChefSpec will use the name of the top-level
295
+ `describe` block as a recipe name to run. So for the case of testing a recipe
296
+ in your cookbook, use the `cookbookname::recipename` string as the label:
412
297
 
413
298
  ```ruby
414
- describe 'example::default' do
415
- let(:chef_run) do
416
- ChefSpec::SoloRunner.new do |node|
417
- node.automatic['memory']['total'] = '512kB'
418
- end.converge(described_recipe)
419
- end
420
- end
299
+ describe 'mycookbook'
300
+ # Or.
301
+ describe 'mycookbook::myrecipe'
421
302
  ```
422
303
 
423
- The `node` that is returned is actually a [`Chef::Node`](https://docs.chef.io/nodes.html) object.
304
+ ### Test Settings
424
305
 
425
- To set an attribute within a specific test, set the attribute in the `it` block and then **(re-)converge the node**:
306
+ Most ChefSpec configuration is set in your example groups (`describe` or `context`
307
+ blocks) using helper methods. These all follow the RSpec convention of inheriting
308
+ from a parent group to the groups inside it. So a setting in your top-level `describe`
309
+ will automatically be set in any `context` unless overridden:
426
310
 
427
311
  ```ruby
428
- describe 'example::default' do
429
- let(:chef_run) { ChefSpec::SoloRunner.new } # Notice we don't converge here
312
+ describe 'something' do
313
+ platform 'ubuntu'
430
314
 
431
- it 'performs the action' do
432
- chef_run.node.normal['cookbook']['attribute'] = 'hello'
433
- chef_run.converge(described_recipe) # The converge happens inside the test
315
+ # Platform is Ubuntu for any tests here.
316
+ it { is_expected.to ... }
434
317
 
435
- expect(chef_run).to do_something
318
+ context 'when something' do
319
+ # Platform is still Ubuntu for any tests here.
436
320
  end
437
- end
438
- ```
439
-
440
- ## Using a Chef Server
441
321
 
442
- All the examples thus far have used the `ChefSpec::SoloRunner`, which runs ChefSpec in Chef Solo mode. ChefSpec also includes the ability to create in-memory Chef Servers. This server can be populated with fake data and used to test search, data bags, and other "server-only" features.
443
-
444
- To use the ChefSpec server, simply replace `ChefSpec::SoloRunner` with `ChefSpec::ServerRunner`:
445
-
446
- ```diff
447
- describe 'example::default' do
448
- - let(:chef_run) { ChefSpec::SoloRunner.converge(described_recipe) }
449
- + let(:chef_run) { ChefSpec::ServerRunner.converge(described_recipe) }
322
+ context 'when something else' do
323
+ platform 'fedora'
324
+ # But platform here will be Fedora.
325
+ end
450
326
  end
451
327
  ```
452
328
 
453
- This will automatically create a Chef Server, synchronize all the cookbooks in your `cookbook_path`, and wire all the internals of Chef together. Recipe calls to `search`, `data_bag` and `data_bag_item` will now query this ChefSpec server.
454
-
455
- ### DSL
329
+ #### Platform Data
456
330
 
457
- The ChefSpec server includes a collection of helpful DSL methods for populating data into the Chef Server.
458
-
459
- Create a client:
331
+ To support simulating Chef runs on the same OS as you use your cookbooks on, ChefSpec
332
+ loads pre-fabricated Ohai data from [Fauxhai](https://github.com/chefspec/fauxhai/).
333
+ To configure which OS' data is set for your test, use the `platform` helper method:
460
334
 
461
335
  ```ruby
462
- ChefSpec::ServerRunner.new do |node, server|
463
- server.create_client('my_client', { admin: true })
336
+ describe 'something' do
337
+ platform 'ubuntu', '18.04'
338
+ # ...
464
339
  end
465
340
  ```
466
341
 
467
- Create a data bag (and items):
342
+ You can specify a partial version number to get the latest version of that OS
343
+ matching the provided prefix, or leave the version off entirely to get the latest
344
+ version overall:
468
345
 
469
346
  ```ruby
470
- ChefSpec::ServerRunner.new do |node, server|
471
- server.create_data_bag('my_data_bag', {
472
- 'item_1' => {
473
- 'password' => 'abc123'
474
- },
475
- 'item_2' => {
476
- 'password' => 'def456'
477
- }
478
- })
479
- end
347
+ # Will use the latest RedHat 7.x.
348
+ platform 'redhat', '7'
349
+ # Will use the latest version of Windows.
350
+ platform 'windows'
480
351
  ```
481
352
 
482
- Create an environment:
353
+ **WARNING:** If you leave off the version or use a partial version prefix, the
354
+ behavior of your tests may change between versions of ChefDK as new data is
355
+ available in Fauxhai. Only use this feature if you're certain that your tests
356
+ do not (or should not) depend on the specifics of OS version.
483
357
 
484
- ```ruby
485
- ChefSpec::ServerRunner.new do |node, server|
486
- server.create_environment('my_environment', { default_attributes: { description: '...' } })
487
- end
488
- ```
358
+ #### Node Attributes
489
359
 
490
- Create a node:
360
+ Node attributes are set using the `default_attributes`, `normal_attributes`,
361
+ `override_attributes`, and `automatic_attributes` helper methods. These inherit
362
+ from a parent group to its children using a deep merge, like in other places in
363
+ Chef:
491
364
 
492
365
  ```ruby
493
- ChefSpec::ServerRunner.new do |node, server|
494
- server.create_node('my_node', { run_list: ['...'] })
495
- end
496
- ```
497
-
498
- Note: the current "node" is always uploaded to the server. However, due to the way the Chef Client compiles cookbooks, you must update the current node on the server if any attributes are changed:
366
+ describe 'something' do
367
+ default_attributes['myapp']['name'] = 'one'
368
+ default_attributes['myapp']['email'] = 'myapp@example.com'
499
369
 
500
- ```ruby
501
- ChefSpec::ServerRunner.new do |node, server|
502
- node.normal['attribute'] = 'value'
503
-
504
- # At this point, the server already has a copy of the current node object due
505
- # to the way Chef compiled the resources. However, that node does not have
506
- # this new value. As such, you must "save" the node back to the server to
507
- # persist this attribute update.
508
- server.update_node(node)
370
+ context 'when something' do
371
+ default_attributes['myapp']['name'] = 'two'
372
+ end
509
373
  end
510
374
  ```
511
375
 
512
- You may also use the `stub_node` macro, which will create a new `Chef::Node` object and accepts the same parameters as the Chef Runner and a Fauxhai object:
513
-
514
- ```ruby
515
- www = stub_node(platform: 'ubuntu', version: '16.04') do |node|
516
- node.normal['attribute'] = 'value'
517
- end
376
+ Any values set using `automatic_attributes` take priority over Fauxhai data.
518
377
 
519
- # `www` is now a local Chef::Node object you can use in your test. To publish
520
- # this node to the server, call `create_node`:
521
-
522
- ChefSpec::ServerRunner.new do |node, server|
523
- server.create_node(www)
524
- end
525
- ```
378
+ #### Step Into
526
379
 
527
- Create a role:
380
+ Normally ChefSpec skips all resource (and provider) actions. When testing the
381
+ implementation of a custom resource, we need to tell ChefSpec to run actions
382
+ on our specific custom resource so it can be tested:
528
383
 
529
384
  ```ruby
530
- ChefSpec::ServerRunner.new do |node, server|
531
- server.create_role('my_role', { default_attributes: {} })
385
+ describe 'something' do
386
+ step_into :my_custom_resource
532
387
  end
533
388
  ```
534
389
 
535
- **NOTE** The ChefSpec server is empty at the start of each example to avoid interdependent tests.
536
390
 
537
- ### Data Store
391
+ #### Other ChefSpec Configuration
538
392
 
539
- The `ServerRunner` has two options for how it will store data: `in_memory` or `on_disk`. The default value is `in_memory`. These two options have different performance implications based on your specific setup. If you are running into performance problems (slow tests, frequent hanging, etc) with one setting, try using the other.
540
-
541
- ## Stubbing
542
-
543
- ### Command
544
-
545
- Given a recipe with shell guard:
393
+ You can specify any other ChefSpec configuration options using the `chefspec_options`
394
+ helper:
546
395
 
547
396
  ```ruby
548
- template '/tmp/foo.txt' do
549
- not_if 'grep text /tmp/foo.txt'
397
+ describe 'something' do
398
+ chefspec_options[:log_level] = :debug
550
399
  end
551
400
  ```
552
401
 
553
- ChefSpec will raise an error like:
554
-
555
- ```text
556
- Real commands are disabled. Unregistered command: `grep text /tmp/foo.txt`
557
-
558
- You can stub this command with:
559
-
560
- stub_command("grep text /tmp/foo.txt").and_return(true)
561
-
562
- ============================================================
563
- ```
564
-
565
- Just like the error message says, you must stub the command result. This can be done inside a `before` block or inside the `it` block, and the stubbing method accepts both a value or Ruby code. If provided a value, the result is static. If provided a Ruby block, the block is evaluated each time the search is called.
566
-
567
- ```ruby
568
- describe 'example::default' do
569
- let(:chef_run) { ChefSpec::SoloRunner.new }
570
-
571
- before do
572
- stub_command("grep text /tmp/foo.txt").and_return(true)
573
- end
574
- end
575
- ```
402
+ ### Stubbing
576
403
 
577
- ```ruby
578
- describe 'example::default' do
579
- let(:chef_run) { ChefSpec::SoloRunner.new }
404
+ In order to keep unit tests fast and independent of the target system, we have to
405
+ make sure that any interaction with the system (either the target node or the Chef
406
+ Server, both parts of the system just in opposite directions) is replaced with a
407
+ fake, local version. For some thing, like ensuring that resource actions are
408
+ replaced with a no-op, the stubbing is automatic. For others, we need to tell ChefSpec
409
+ how to handle things.
580
410
 
581
- before do
582
- stub_command("grep text /tmp/foo.txt") { rand(50)%2 == 0 }
583
- end
584
- end
585
- ```
411
+ #### Guards
586
412
 
587
- The stubbed command can also be passed as a regular expression, allowing multiple commands to be stubbed with one line.
413
+ The most common case of interacting with the system is a guard clause on a resource:
588
414
 
589
415
  ```ruby
590
- describe 'example::default' do
591
- let(:chef_run) { ChefSpec::SoloRunner.new }
592
-
593
- before do
594
- stub_command(/(foo)|(bar)/).and_return(true)
595
- end
596
- end
416
+ not_if 'some command'
417
+ # Or.
418
+ only_if 'some command'
597
419
  ```
598
420
 
599
- ### Library Helpers
600
-
601
- Given a library helper with a `has_bacon?` method:
421
+ In order for ChefSpec to know how to evaluate the resource, we need to tell it
422
+ how the command would have returned for this test if it was running on the actual
423
+ machine:
602
424
 
603
425
  ```ruby
604
- module Demo
605
- module Helper
606
-
607
- include Chef::Mixin::ShellOut
608
-
609
- def has_bacon?
610
- cmd = shell_out!('getent passwd bacon', {:returns => [0,2]})
611
- cmd.stderr.empty? && (cmd.stdout =~ /^bacon/)
426
+ describe 'something' do
427
+ recipe do
428
+ execute '/opt/myapp/install.sh' do
429
+ # Check if myapp is installed and runnable.
430
+ not_if 'myapp --version'
612
431
  end
613
432
  end
614
- end
615
- ```
616
-
617
- Stub the output of the library helper. [Additional information](http://jtimberman.housepub.org/blog/2015/05/30/quick-tip-stubbing-library-helpers-in-chefspec/)
618
-
619
- ```ruby
620
- before do
621
- allow_any_instance_of(Chef::Node).to receive(:has_bacon?).and_return(true)
622
- end
623
- ```
624
-
625
- ### Data Bag & Data Bag Item
626
-
627
- **NOTE** This is not required if you are using a ChefSpec server.
628
-
629
- Given a recipe that executes a `data_bag` method:
630
-
631
- ```ruby
632
- data_bag('users').each do |user|
633
- data_bag_item('users', user['id'])
634
- end
635
- ```
636
-
637
- ChefSpec will raise an error like:
638
-
639
- ```text
640
- Real data_bags are disabled. Unregistered data_bag: data_bag(:users)
641
-
642
- You can stub this data_bag with:
643
-
644
- stub_data_bag("users").and_return([])
645
-
646
- ============================================================
647
- ```
648
-
649
- Just like the error message says, you must stub the result of the `data_bag` call. This can be done inside a `before` block or inside the `it` block, and the stubbing method accepts both a value or Ruby code. If provided a value, the result is static. If provided a Ruby block, the block is evaluated each time the search is called.
650
-
651
- ```ruby
652
- describe 'example::default' do
653
- let(:chef_run) { ChefSpec::SoloRunner.new }
654
-
655
- before do
656
- stub_data_bag('users').and_return([])
657
- end
658
- end
659
- ```
660
-
661
- ```ruby
662
- describe 'example::default' do
663
- let(:chef_run) { ChefSpec::SoloRunner.new }
664
-
665
- before do
666
- stub_data_bag('users').and_return(['svargo', 'francis'])
667
-
668
- stub_data_bag_item('users', 'svargo').and_return({ ... })
669
- stub_data_bag_item('users', 'francis') { (ruby code) }
670
- end
671
- end
672
- ```
673
433
 
674
- If you are using **Encrypted Data Bag Items**, you'll need to dive into the RSpec layer and stub that class method instead:
675
-
676
- ```ruby
677
- describe 'example::default' do
678
434
  before do
679
- allow(Chef::EncryptedDataBagItem).to receive(:load).with('users', 'svargo').and_return(...)
435
+ # Tell ChefSpec the command would have succeeded.
436
+ stub_command('myapp --version').and_return(true)
437
+ # Tell ChefSpec the command would have failed.
438
+ stub_command('myapp --version').and_return(false)
439
+ # You can also use a regexp to stub multiple commands at once.
440
+ stub_command(/^myapp/).and_return(false)
680
441
  end
681
442
  end
682
443
  ```
683
444
 
684
- ### Search
685
-
686
- **NOTE** This is not required if you are using a ChefSpec server.
687
-
688
- Because ChefSpec is a unit-testing framework, it is recommended that all third-party API calls be mocked or stubbed. ChefSpec exposes a helpful RSpec macro for stubbing search results in your tests. If you converge a Chef recipe that implements a `search` call, ChefSpec will throw an error like:
689
-
690
- ```text
691
- Real searches are disabled. Unregistered search: search(:node, 'name:hello')
445
+ If using the Ruby code block form of a guard (e.g. `not_if { something }`), see
446
+ the [Ruby stubbing section](#ruby-code) below.
692
447
 
693
- You can stub this search with:
448
+ #### Search
694
449
 
695
- stub_search(:node, 'name:hello') { }
696
-
697
- ============================================================
698
- ```
699
-
700
- Just like the error message says, you must stub the search result. This can be done inside a `before` block or inside the `it` block, and the stubbing method accepts both a value or Ruby code. If provided a value, the result is static. If provided a Ruby block, the block is evaluated each time the search is called.
450
+ When testing code that uses the `search()` API in Chef, we have to stub out the
451
+ results that would normally come from the Chef Server:
701
452
 
702
453
  ```ruby
703
- describe 'example::default' do
704
- let(:chef_run) { ChefSpec::SoloRunner.new }
705
-
706
- before do
707
- stub_search(:node, 'name:hello').and_return([])
454
+ describe 'something' do
455
+ recipe do
456
+ web_servers = search(:node, 'roles:web').map { |n| n['hostname'] }
708
457
  end
709
- end
710
- ```
711
-
712
- ```ruby
713
- describe 'example::default' do
714
- let(:chef_run) { ChefSpec::SoloRunner.new }
715
458
 
716
459
  before do
717
- stub_search(:node, 'name:hello') { (ruby_code) }
460
+ stub_search(:node, 'roles:web').and_return([{hostname: 'one'}, {hostname: two}])
718
461
  end
719
462
  end
720
463
  ```
721
464
 
722
- ### Ruby libraries (File, FileUtils, etc)
465
+ #### Data Bags
723
466
 
724
- When stubbing core ruby libraries, users must be aware that there is no differentiation between your cookbook code that calls `File.exist?` and core chef code (e.g. the cookbook loader) that calls `File.exist?`. If you stub or setup an expectation without qualifying the arguments then you will stub that method for all core chef code as well. Also note that if you setup an expectation on a particular argument that invoking the method with any other argument will throw an unexpected argument error out of rspec, so you must setup an allowance using `.and_call_original` to avoid breaking core chef.
467
+ Similar to the Search API, the `data_bag()` and `data_bag_item()` APIs normally
468
+ fetch data from Chef Server so we need to stub their results:
725
469
 
726
470
  ```ruby
727
- describe 'example::default' do
728
- let(:chef_run) { ChefSpec::SoloRunner.new }
729
-
730
- before do
731
- # avoid breaking all of core chef wherever it calls File.exist? with other arguments
732
- allow(File).to receive(:exist?).and_call_original
733
- end
734
-
735
- it "tests something when /etc/myfile.txt does not exist" do
736
- # only setup an expectation on our file
737
- expect(File).to receive(:exist?).with("/etc/myfile.txt").and_return(false)
738
- [ ... test that the chef resource collection is constructed correctly in this case ... ]
739
- end
740
- end
741
- ```
742
-
743
- This is basic usage of rspec and not specific to chefspec. It applies to any class method in `File`, `Dir`, `FileUtils`, `IO` or any other ruby library. In general any time you `expect(Some::Symbol).to receive(:a_method).and_return(value)` you run the risk of breaking other code unless you isolate your mocking or expectation down to only the arguments which your code uses.
744
-
745
- ## Reporting
746
-
747
- **NOTE: The coverage reporting feature is deprecated and will be removed in a future version. This documentation exists only for reference purposes.**
748
-
749
- ChefSpec can generate a report of resources read over resources tested.
750
-
751
- To generate the coverage report, add the following to your `spec_helper.rb` before you require any "Chef" code:
752
-
753
- ```ruby
754
- require 'chefspec'
755
- ChefSpec::Coverage.start!
756
-
757
- # Existing spec_helper contents...
758
- ```
759
-
760
- By default, that method will output helpful information to standard out:
761
-
762
- ```text
763
- ChefSpec Coverage report generated...
764
-
765
- Total Resources: 6
766
- Touched Resources: 1
767
- Touch Coverage: 16.67%
768
-
769
- Untouched Resources:
770
-
771
- package[git] bacon/recipes/default.rb:2
772
- package[build-essential] bacon/recipes/default.rb:3
773
- package[apache2] bacon/recipes/default.rb:4
774
- package[libvirt] bacon/recipes/default.rb:5
775
- package[core] bacon/recipes/default.rb:6
776
- ```
777
-
778
- By default, ChefSpec will test all cookbooks that are loaded as part of the Chef Client run. If you have a cookbook with many dependencies, this may be less than desirable. To restrict coverage reporting against certain cookbooks, `ChefSpec::Coverage` yields a block:
779
-
780
- ```ruby
781
- ChefSpec::Coverage.start! do
782
- add_filter 'vendor/cookbooks'
783
- end
784
- ```
785
-
786
- The `add_filter` method accepts a variety of objects. For example:
787
-
788
- ```ruby
789
- ChefSpec::Coverage.start! do
790
- # Strings are interpreted as file paths, with a forward anchor
791
- add_filter 'vendor/cookbooks'
792
-
793
- # Regular expressions must be escaped, but provide a nicer API for negative
794
- # back tracking
795
- add_filter /cookbooks\/(?!omnibus)/
796
-
797
- # Custom block filters yield a {Chef::Resource} object - if the block
798
- # evaluates to true, it will be filtered
799
- add_filter do |resource|
800
- # Bob's cookbook's are completely untested! Ignore them until he gets his
801
- # shit together.
802
- resource.source_file =~ /cookbooks\/bob-(.+)/
803
- end
804
- end
805
- ```
806
-
807
- For more complex scenarios, you can create a custom `Filter` object that inherits from `ChefSpec::Coverage::Filter` and implements the `matches?` method.
808
-
809
- ```ruby
810
- class CustomFilter < ChefSpec::Coverage::Filter
811
- def initialize(arg1, arg2, █)
812
- # Create a custom initialization method, do some magic, etc.
471
+ describe 'something' do
472
+ recipe do
473
+ # Side note: don't write recipe code like this. This should be `search(:users, '*:*')`.
474
+ users = data_bag('users').map do |user|
475
+ data_bag_item('users', user['id'])
476
+ end
813
477
  end
814
478
 
815
- def matches?(resource)
816
- # Custom matching logic in here - anything that evaluates to "true" will be
817
- # filtered.
479
+ before do
480
+ stub_data_bag('users').and_return(['asmithee'])
481
+ stub_data_bag_item('users', 'asmithee').and_return({uid: 1234})
818
482
  end
819
483
  end
820
-
821
- ChefSpec::Coverage.start! do
822
- add_filter CustomFilter.new('foo', :bar)
823
- end
824
- ```
825
-
826
- If you are using ChefSpec's Berkshelf plugin, a filter is automatically created for you. If you would like to ignore that filter, you can `clear` all the filters before defining your own:
827
-
828
- ```ruby
829
- ChefSpec::Coverage.start! do
830
- filters.clear
831
-
832
- # Add your custom filters now
833
- end
834
- ```
835
-
836
- If you would like a different output format for the Coverage.report! output, you can specify one of the three built-in templates, or supply your own by calling the set_template in the `ChefSpec::Coverage` block:
837
-
838
- ```ruby
839
- ChefSpec::Coverage.start! do
840
- set_template 'json.erb'
841
- end
842
484
  ```
843
485
 
844
- Provided templates are human.erb_(default)_, table.erb and json.erb, to supply a custom template, specify a relative(to run directory) or absolute path.
845
-
846
- ```ruby
847
- ChefSpec::Coverage.start! do
848
- set_template '/opt/custom/templates/verbose.erb'
849
- end
850
- ```
486
+ #### Resource and Provider Methods
851
487
 
852
- If you would like to add alternative reporting for the Coverage.report! ouput, you can supply your own by calling add_output in the `ChefSepc::Coverage` block: Note the reportOutput has the following items in it: total, touched, coverage and collections of untouched_resources and all_resources
488
+ When testing custom resources, it is often useful to stub methods on the resource
489
+ or provider instance. These can be set up using the `stubs_for_resource` and
490
+ `stubs_for_provider` helpers:
853
491
 
854
492
  ```ruby
855
- ChefSpec::Coverage.start! do
856
- add_output do |reportOutput|
857
- File.open( "coverage.json","w" ) do |f|
858
- f.puts(reportOutput[:total])
859
- f.puts(reportOutput[:touched])
860
- f.puts(reportOutput[:coverage])
861
- f.puts(reportOutput[:untouched_resources])
862
- f.puts(reportOutput[:all_resources])
863
- end
493
+ describe 'something' do
494
+ recipe do
495
+ my_custom_resource 'something'
864
496
  end
865
- end
866
- ```
867
-
868
- Note the above example outputs the raw data without applying formatting.
869
-
870
- ## Mocking Out Environments
871
-
872
- ### ServerRunner
873
497
 
874
- ```ruby
875
- ChefSpec::ServerRunner.new do |node, server|
876
- # Create the environment
877
- server.create_environment('staging', { default_attributes: { cookbook_attr: 'value' } })
878
-
879
- # Assign the environment to the node
880
- node.chef_environment = 'staging'
881
- end
882
- ```
883
-
884
- ### SoloRunner
885
-
886
- If you want to mock out `node.chef_environment`, you'll need to use RSpec mocks/stubs twice:
887
-
888
- ```ruby
889
- let(:chef_run) do
890
- ChefSpec::SoloRunner.new do |node|
891
- # Create a new environment (you could also use a different :let block or :before block)
892
- env = Chef::Environment.new
893
- env.name 'staging'
894
-
895
- # Stub the node to return this environment
896
- allow(node).to receive(:chef_environment).and_return(env.name)
897
-
898
- # Stub any calls to Environment.load to return this environment
899
- allow(Chef::Environment).to receive(:load).and_return(env)
900
- end.converge('cookbook::recipe')
901
- end
902
- ```
903
-
904
- **There is probably a better/easier way to do this. If you have a better solution, please open an issue or Pull Request so we can make this less painful :)**
905
-
906
- ## Testing LWRPs
907
-
908
- **WARNING** Cookbooks with dashes (hyphens) are difficult to test with ChefSpec because of how Chef classifies objects. We recommend naming cookbooks with underscores (`_`) instead of dashes (`-`).
909
-
910
- ChefSpec overrides all providers to take no action (otherwise it would actually converge your system). This means that the steps inside your LWRP are not actually executed. If an LWRP performs actions, those actions are never executed or added to the resource collection.
911
-
912
- In order to run the actions exposed by your LWRP, you have to explicitly tell the `Runner` to step into it:
913
-
914
- ```ruby
915
- require 'chefspec'
916
-
917
- describe 'foo::default' do
918
- let(:chef_run) do
919
- ChefSpec::SoloRunner.new(step_into: ['my_lwrp']).converge('foo::default')
498
+ # Set up stubs for just the one resource.
499
+ stubs_for_resource('my_custom_resource[something]') do |res|
500
+ # Can use any RSpec Mocks code here, see below.
501
+ allow(res).to receive(:something)
920
502
  end
921
-
922
- it 'installs the foo package through my_lwrp' do
923
- expect(chef_run).to install_package('foo')
503
+ # Stubs for any instance of my_custom_resource.
504
+ stubs_for_resource('my_custom_resource') do |res|
505
+ # ...
924
506
  end
925
- end
926
- ```
927
-
928
- **NOTE:** If your cookbook exposes LWRPs, it is highly recommended you also create a `libraries/matchers.rb` file as outlined below in the "Packaging Custom Matchers" section. **You should never `step_into` an LWRP unless you are testing it. Never `step_into` an LWRP from another cookbook!**
929
-
930
- ## Automatic Matchers
931
-
932
- As of ChefSpec 7.1.0 there are "custom" matchers generated for all internal core-chef resources, along with any LWRPs/HWRPs/Custom Resources that are user-defined in cookbooks.
933
-
934
- The matchers follow the standard custom of `<action>_<resource_name>` with the exception of the `create_if_missing` action which _also_ gets a `create_<resource_name>_if_missing` matcher.
935
-
936
- Matchers should be wired up for the `resource_name` of the resource along with all define `provides` lines synonyms and any `action` methods or `allowed_actions`.
937
-
938
- There should be little reason to package custom matchers in cookbooks any more, but the approach below still works if there are special matchers which cookbooks wish to expose which do not follow the automatically generated pattern.
939
-
940
- ## Packaging Custom Matchers
941
-
942
- ChefSpec exposes the ability for cookbook authors to package custom matchers inside a cookbook so that other developers may take advantage of them in testing. This is done by creating a special library file in the cookbook named `matchers.rb`:
943
-
944
- ```ruby
945
- # cookbook/libraries/matchers.rb
946
-
947
- if defined?(ChefSpec)
948
- def my_custom_matcher(resource_name)
949
- ChefSpec::Matchers::ResourceMatcher.new(resource, action, resource_name)
507
+ # Stubs for any resource.
508
+ stubs_for_resource do |res|
509
+ # ...
950
510
  end
951
- end
952
- ```
953
-
954
- 1. The entire contents of this file must be wrapped with the conditional clause checking if `ChefSpec` is defined.
955
- 2. Each matcher is actually a top-level method. The above example corresponds to the following RSpec test:
956
-
957
- ```ruby
958
- expect(chef_run).to my_custom_matcher('...')
959
- ```
960
-
961
- 3. `ChefSpec::Matchers::ResourceMatcher` accepts three parameters:
962
-
963
- 1. The name of the resource to find in the resource collection (i.e. the name of the LWRP).
964
- 2. The action that resource should receive.
965
- 3. The value of the name attribute of the resource to find. (This is typically proxied as the value from the matcher definition.)
966
-
967
- ChefSpec's built-in `ResourceMatcher` _should_ satisfy most common use cases for packaging a custom matcher with your LWRPs. However, if your cookbook is extending Chef core or is outside of the scope of a traditional "resource", you may need to create a custom matcher. For more information on custom matchers in RSpec, please [watch the Railscast on Custom Matchers](http://railscasts.com/episodes/157-rspec-matchers-macros) or look at some of the other custom matchers in ChefSpec's source code.
968
-
969
- ### Example
970
-
971
- Suppose I have a cookbook named "motd" with a resource/provider "message".
972
-
973
- ```ruby
974
- # motd/resources/message.rb
975
- actions :write
976
- default_action :write
977
-
978
- attribute :message, name_attribute: true
979
- ```
980
-
981
- ```ruby
982
- # motd/providers/message.rb
983
- action :write do
984
- # ...
985
- end
986
- ```
987
-
988
- Chef will dynamically build the `motd_message` LWRP at runtime that can be used in the recipe DSL:
989
-
990
- ```ruby
991
- motd_message 'my message'
992
- ```
993
511
 
994
- You can package a custom ChefSpec matcher with the motd cookbook by including the following code in `libraries/matchers.rb`:
995
-
996
- ```ruby
997
- # motd/libraries/matchers.rb
998
- if defined?(ChefSpec)
999
- def write_motd_message(message)
1000
- ChefSpec::Matchers::ResourceMatcher.new(:motd_message, :write, message)
1001
- end
1002
- end
1003
- ```
1004
-
1005
- Other developers can write RSpec tests against your LWRP in their cookbooks:
1006
-
1007
- ```ruby
1008
- expect(chef_run).to write_motd_message('my message')
1009
- ```
1010
-
1011
- **Don't forget to include documentation in your cookbook's README noting the custom matcher and its API!**
1012
-
1013
- As a caveat, if your custom LWRP uses a custom `provides` value as shown below (Chef 12+), you will need to package slightly different custom matchers:
1014
-
1015
- ```ruby
1016
- # motd/resources/message.rb
1017
- actions :write
1018
- default_action :write
1019
-
1020
- provides :foobar
1021
-
1022
- attribute :message, name_attribute: true
1023
- ```
1024
-
1025
- With a custom `provides` declaration, the resource is still inserted into the resource collection with its generic name; `provides` is just sugar for use in the recipe. As such, you will also need to introduce sugar into your custom matchers:
1026
-
1027
- ```ruby
1028
- # motd/libraries/matchers.rb
1029
- if defined?(ChefSpec)
1030
- def write_foobar(message)
1031
- ChefSpec::Matchers::ResourceMatcher.new(:motd_message, :write, message)
512
+ # Stubs for the provider for just the one resource.
513
+ stubs_for_provider('my_custom_resource[something]') do |res|
514
+ # Can use any RSpec Mocks code here, see below.
515
+ allow(res).to receive(:something)
1032
516
  end
517
+ # And similar to the above for any provider of a type or any overall.
1033
518
  end
1034
519
  ```
1035
520
 
1036
- Notice that we have changed the name of the method to match the "foobar" action, but the resource matcher definition remains unchanged. When the Chef run executes, the resource will be inserted into the collection as `motd_message`, even though it was given a custom provides.
1037
-
1038
- ## Writing Custom Matchers
1039
-
1040
- If you are testing a cookbook that does not package its LWRP matchers, you can create your own following the same pattern as the "Packaging Custom Matchers" section. Simply, create a file at `spec/support/matchers.rb` and add your resource matchers:
1041
-
1042
- ```ruby
1043
- # spec/support/matchers.rb
1044
- def my_custom_matcher(resource_name)
1045
- ChefSpec::Matchers::ResourceMatcher.new(:resource, :action, resource_name)
1046
- end
1047
- ```
1048
-
1049
- Then require this file in your `spec_helper.rb` so the matcher can be used:
1050
-
1051
- ```ruby
1052
- require_relative 'support/matchers'
1053
- ```
1054
-
1055
- Please use this as a _temporary_ solution. Consider sending a Pull Request to the LWRP author(s) packaging the custom resource matchers (see previous section).
1056
-
1057
- ## Matchers for looking up custom resources
1058
-
1059
- ChefSpec also provides a helper method to define a method on the Chef runner for locating a resource in the collection. This is helpful while asserting against custom resource notifications.
1060
-
1061
- ```ruby
1062
- # matchers.rb
1063
- ChefSpec.define_matcher :my_custom_resource
1064
- ```
1065
-
1066
- And then in your spec suite, you can obtain the custom resource for assertions:
1067
-
1068
- ```ruby
1069
- let(:chef_run) { ChefSpec::SoloRunner.converge('...') }
1070
-
1071
- it 'notifies the thing' do
1072
- custom = chef_run.my_custom_resource('name')
1073
- expect(custom).to notify('service[apache2]').to(:restart).immediately
1074
- end
1075
- ```
1076
-
1077
- You can use this functionality to bundle lookup matchers with cookbooks, or to provide your own when the upstream cookbook doesn't include it.
1078
-
1079
- ## Expecting Exceptions
1080
-
1081
- In Chef 11, custom formatters were introduced and ChefSpec uses a custom formatter to suppress Chef Client output. In the event of a convergence failure, ChefSpec will output the error message from the run to help you debug:
1082
-
1083
- ```text
1084
- ================================================================================
1085
- Recipe Compile Error in apt_package/recipes/install.rb
1086
- ================================================================================
1087
-
1088
- RuntimeError
1089
- ------------
1090
- RuntimeError
1091
-
1092
- Cookbook Trace:
1093
- ---------------
1094
- .../apt_package/recipes/install.rb:1:in `from_file'
1095
- .../apt_package/spec/install_spec.rb:4:in `block (2 levels) in <top (required)>'
1096
- .../apt_package/spec/install_spec.rb:7:in `block (2 levels) in <top (required)>'
1097
-
1098
- Relevant File Content:
1099
- ----------------------
1100
- .../apt_package/recipes/install.rb:
1101
-
1102
- 1>> raise RuntimeError
1103
- 2:
1104
- 3: apt_package 'default_action'
1105
- ```
1106
-
1107
- This output is automatically silenced when using RSpec's `raise_error` matcher:
1108
-
1109
- ```ruby
1110
- let(:chef_run) { ChefSpec::SoloRunner.converge('cookbook::recipe') }
1111
-
1112
- it 'raises an error' do
1113
- expect {
1114
- chef_run
1115
- }.to raise_error
1116
- end
1117
- ```
1118
-
1119
- You can also assert that a particular error was raised. If the error matches the given type, the output is suppressed. If not, the test fails and the entire stack trace is presented.
1120
-
1121
- ```ruby
1122
- let(:chef_run) { ChefSpec::SoloRunner.converge('cookbook::recipe') }
1123
-
1124
- it 'raises an error' do
1125
- expect {
1126
- chef_run
1127
- }.to raise_error(RuntimeError)
1128
- end
1129
- ```
1130
-
1131
- ## Testing Multiple Recipes
1132
-
1133
- Even though ChefSpec is cookbook-centric, you can still converge multiple recipes in a single `ChefSpec::SoloRunner` instance. Given a cookbook "sandwich" with recipes "bacon", "lettuce" and "tomato":
1134
-
1135
- ```ruby
1136
- # cookbooks/sandwich/recipes/bacon.rb
1137
- package 'bacon'
1138
-
1139
- # cookbooks/sandwich/recipes/lettuce.rb
1140
- package 'lettuce'
1141
-
1142
- # cookbooks/sandwich/recipes/tomato.rb
1143
- package 'tomato'
1144
- ```
1145
-
1146
- ```ruby
1147
- let(:chef_run) { ChefSpec::SoloRunner.converge('sandwich::bacon', 'sandwich::lettuce', 'sandwich::tomato') }
1148
- ```
1149
-
1150
- ```ruby
1151
- expect(chef_run).to install_package('bacon')
1152
- expect(chef_run).to install_package('lettuce')
1153
- expect(chef_run).to install_package('tomato')
1154
- ```
1155
-
1156
- ## Testing Roles
1157
-
1158
- Roles can also be used in a single `ChefSpec::SoloRunner` instance. Given a cookbook "bacon" with a default recipe:
1159
-
1160
- ```ruby
1161
- # cookbooks/bacon/recipes/default.rb
1162
- package 'foo'
1163
- ```
1164
-
1165
- and a default attributes file:
1166
-
1167
- ```ruby
1168
- # cookbooks/bacon/attributes/default.rb
1169
- default['bacon']['temperature'] = 200
1170
- ```
1171
-
1172
- and a role "breakfast":
1173
-
1174
- ```ruby
1175
- # roles/breakfast.rb
1176
- default_attributes(
1177
- 'bacon' => {
1178
- 'temperature' => 150 # NOTE: This is different from the default value
1179
- }
1180
- )
1181
- run_list([
1182
- 'recipe[bacon::default]'
1183
- ])
1184
- ```
1185
-
1186
- You can test that the role is appropriately applied by telling the `ChefSpec::SoloRunner` to converge on the _role_ instead of a recipe:
1187
-
1188
- ```ruby
1189
- let(:chef_run) { ChefSpec::SoloRunner.converge('role[breakfast]') }
1190
- ```
1191
-
1192
- Assert that the run_list is properly expanded:
1193
-
1194
- ```ruby
1195
- expect(chef_run).to include_recipe('bacon::default')
1196
- ```
1197
-
1198
- Assert that the correct attribute is used:
521
+ By default, stubs for the resource will also be set up on the `current_resource`
522
+ object. This can be disabled by using `stubs_for_resource('my_custom_resource[something]', current_resource: false)`.
523
+ You can also manually set stubs for only the `current_resource` using `stubs_for_current_resource`.
1199
524
 
1200
- ```ruby
1201
- expect(chef_run.node['bacon']['temperature']).to eq(150)
1202
- ```
525
+ #### Ruby Code
1203
526
 
1204
- **NOTE** If your roles live somewhere outside of the expected path, you must set `RSpec.config.role_path` to point to the directory containing your roles **before** invoking the `#converge` method!
527
+ For more complex Ruby code, in recipes, libraries, or custom resources, you have
528
+ the full power of RSpec and RSpec Mocks available to you:
1205
529
 
1206
530
  ```ruby
1207
- RSpec.configure do |config|
1208
- config.role_path = '/var/my/roles' # global setting
531
+ before do
532
+ allow(File).to receive(:exist?).and_call_original
533
+ allow(File).to receive(:exist?).with('/test/path').and_return(true)
1209
534
  end
1210
-
1211
- # - OR -
1212
-
1213
- ChefSpec::SoloRunner.new(role_path: '/var/my/roles') # local setting
1214
- ```
1215
-
1216
- ## Faster Specs
1217
-
1218
- ChefSpec aims to provide the easiest and simplest path for new users to write RSpec examples for Chef cookbooks. In doing so, it makes some sacrifices in terms of speed and agility of execution. In other words, ChefSpec favors "speed to develop" over "speed to execute". Many of these decisions are directly related to the way Chef dynamically loads resources at runtime.
1219
-
1220
- If you understand how RSpec works and would like to see some significant speed improvements in your specs, you can use the `ChefSpec::Cacher` module inspired by [Juri Timošin](https://github.com/DracoAter). Simply convert all your `let` blocks to `cached`:
1221
-
1222
- ```ruby
1223
- # before
1224
- let(:chef_run) { ChefSpec::SoloRunner.new }
1225
-
1226
- # after
1227
- cached(:chef_run) { ChefSpec::SoloRunner.new }
1228
535
  ```
1229
536
 
1230
- Everything else should work the same. Be advised, as the method name suggests, this will cache the results of your Chef Client Run for the **entire RSpec example**. This makes stubbing more of a challenge, since the node is already converged. For more information, please see [Juri Timošin's blog post on faster specs](http://dracoater.blogspot.com/2013/12/testing-chef-cookbooks-part-25-speeding.html) as well as the discussion in [#275](https://github.com/chefspec/chefspec/issues/275).
1231
-
1232
- ## Media & Third-party Tutorials
1233
-
1234
- - [CustomInk's Testing Chef Cookbooks](http://technology.customink.com/blog/2012/08/03/testing-chef-cookbooks/)
1235
- - [Jake Vanderdray's Practical ChefSpec](http://files.meetup.com/1780846/ChefSpec.pdf)
1236
- - [Jim Hopp's excellent Test Driven Development for Chef Practitioners](http://www.youtube.com/watch?v=o2e0aZUAVGw)
1237
- - [Joshua Timberman's Starting ChefSpec Examples](http://jtimberman.housepub.org/blog/2013/05/09/starting-chefspec-example/)
1238
- - [Juri Timošin's post on faster specs](http://dracoater.blogspot.com/2013/12/testing-chef-cookbooks-part-25-speeding.html)
1239
- - [Seth Vargo's Chef recipe code coverage](https://sethvargo.com/chef-recipe-code-coverage/)
1240
- - [Seth Vargo's TDDing tmux talk](http://www.confreaks.com/videos/2364-mwrc2013-tdding-tmux)
1241
- - [Stephen Nelson Smith's Test-Driven Infrastructure with Chef](http://shop.oreilly.com/product/0636920030973.do)
537
+ Check out the [RSpec Mocks documentation](https://relishapp.com/rspec/rspec-mocks/docs)
538
+ for more information about setting up Ruby method stubs.
1242
539
 
1243
540
  ## Development
1244
541