Unit tests are used to verify that your Chef code matches your requirements. There is often some confusion around this. Many people wonder, “Why am I testing that Chef created a file? Unless there’s some very serious bug in Chef…?”
But you’re not actually testing that Chef created the file, but rather confirming that you as the Chef developer told Chef to create that file; and, more importantly, that it has the correct owner, mode and contents.
If you take a test-driven development (TDD) approach, you might define the Chef unit tests first (make sure this package is installed, make sure this configuration file exists and is owned by some actual user, etc.). Then you would implement your recipes and verify that they met your requirements by running the unit tests.
By design, the unit tests are quick to run. ChefSpec, the unit testing framework, will spin up an in-memory Chef Server (or you can use Chef Solo) and run your recipes in a sort of “no-op” mode. Besides preventing damage to your system, this also reduces the amount of mocking-up you need to perform. When your recipes contain “execute” resources, ChefSpec will fail with an appropriate message on how to stub the command. There are other cases where you’ll need to stub a method call or command, but these arise when you’re using more advanced features like Chef Search or Chef Vault.
The integration tests allow you to test the results of a real Chef run. Test Kitchen will create an in-memory Chef Server (or you can use Chef Solo) and a virtual machine, and it will install and run Chef on that virtual machine against the in-memory Chef Server. Test Kitchen will then execute the tests you defined against the virtual machine’s configuration. In this way you could verify that a service is listening on a particular port, for example.
As with any other code, it’s important to keep testing in mind when implementing Chef recipes, because certain implementations might be easier or harder to test.