{"generator":"Jekyll","link":[{"@attributes":{"href":"https:\/\/fabioperrella.github.io\/feed.xml","rel":"self","type":"application\/atom+xml"}},{"@attributes":{"href":"https:\/\/fabioperrella.github.io\/","rel":"alternate","type":"text\/html"}}],"updated":"2023-02-16T14:00:14+00:00","id":"https:\/\/fabioperrella.github.io\/feed.xml","title":"Fabio Perrella\u2019s Blog","subtitle":"Fabio Perrella's blog","entry":[{"title":"Ruby\u2019s Hidden Gems: Bullet (Appsignal\u2019s blog)","link":{"@attributes":{"href":"https:\/\/fabioperrella.github.io\/2021\/08\/11\/bullet-gem.html","rel":"alternate","type":"text\/html","title":"Ruby\u2019s Hidden Gems: Bullet (Appsignal\u2019s blog)"}},"published":"2021-08-11T00:00:00+00:00","updated":"2021-08-11T00:00:00+00:00","id":"https:\/\/fabioperrella.github.io\/2021\/08\/11\/bullet-gem","content":{"@attributes":{"type":"html"}},"author":{"name":{}},"summary":{"@attributes":{"type":"html"}}},{"title":"Spelunking Ruby Gems (WeTransfer\u2019s blog)","link":{"@attributes":{"href":"https:\/\/fabioperrella.github.io\/2021\/05\/11\/spelunking-ruby-gems.html","rel":"alternate","type":"text\/html","title":"Spelunking Ruby Gems (WeTransfer\u2019s blog)"}},"published":"2021-05-11T00:00:00+00:00","updated":"2021-05-11T00:00:00+00:00","id":"https:\/\/fabioperrella.github.io\/2021\/05\/11\/spelunking-ruby-gems","content":{"@attributes":{"type":"html"}},"author":{"name":{}},"summary":{"@attributes":{"type":"html"}}},{"title":"Using client stubs for easy and reliable integration tests","link":{"@attributes":{"href":"https:\/\/fabioperrella.github.io\/2020\/12\/31\/using-client-stubs-for-easy-and-reliable-integration-tests.html","rel":"alternate","type":"text\/html","title":"Using client stubs for easy and reliable integration tests"}},"published":"2020-12-31T00:00:00+00:00","updated":"2020-12-31T00:00:00+00:00","id":"https:\/\/fabioperrella.github.io\/2020\/12\/31\/using-client-stubs-for-easy-and-reliable-integration-tests","content":"<p>A while ago, I used <a href=\"https:\/\/aws.amazon.com\/sdk-for-ruby\/\">AWS Ruby SDK<\/a> to create a method to download files from S3, and I was introduced to <a href=\"https:\/\/docs.aws.amazon.com\/sdk-for-ruby\/v3\/api\/Aws\/ClientStubs.html\">Aws::ClientStubs<\/a>, which is amazing, and it opened up my mind on how to test external APIs with a more integrated approach!<\/p>\n\n<p>Before discovering <code class=\"language-plaintext highlighter-rouge\">Aws::ClientStubs<\/code>, I wondered how to test this new code, maybe I could use the gem <a href=\"https:\/\/github.com\/vcr\/vcr\">VCR<\/a>.<\/p>\n\n<p>For those of you who haven\u2019t read my <a href=\"\/10_tips_to_help_using_the_VCR_gem_in_your_ruby_test_suite.html\">previous post<\/a>, the most popular one is related to VCR, which would be my default way to test it.<\/p>\n\n<h2 id=\"how-to-use-awsclientstubs\">How to use Aws::ClientStubs<\/h2>\n\n<p>Supposing the following method to download a file from S3, which returns a temporary file with the file content:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">require<\/span> <span class=\"s1\">'aws-sdk-s3'<\/span>\n\n<span class=\"k\">class<\/span> <span class=\"nc\">S3Downloader<\/span>\n  <span class=\"k\">def<\/span> <span class=\"nf\">initialize<\/span><span class=\"p\">(<\/span><span class=\"ss\">s3_client: <\/span><span class=\"no\">Aws<\/span><span class=\"o\">::<\/span><span class=\"no\">S3<\/span><span class=\"o\">::<\/span><span class=\"no\">Client<\/span><span class=\"p\">.<\/span><span class=\"nf\">new<\/span><span class=\"p\">)<\/span>\n    <span class=\"vi\">@s3_client<\/span> <span class=\"o\">=<\/span> <span class=\"n\">s3_client<\/span>\n  <span class=\"k\">end<\/span>\n\n  <span class=\"k\">def<\/span> <span class=\"nf\">download<\/span><span class=\"p\">(<\/span><span class=\"n\">key<\/span><span class=\"p\">:,<\/span> <span class=\"n\">bucket<\/span><span class=\"p\">:)<\/span>\n    <span class=\"n\">tempfile<\/span> <span class=\"o\">=<\/span> <span class=\"no\">Tempfile<\/span><span class=\"p\">.<\/span><span class=\"nf\">new<\/span>\n\n    <span class=\"vi\">@s3_client<\/span><span class=\"p\">.<\/span><span class=\"nf\">get_object<\/span><span class=\"p\">(<\/span>\n      <span class=\"ss\">response_target: <\/span><span class=\"n\">tempfile<\/span><span class=\"p\">,<\/span>\n      <span class=\"ss\">bucket: <\/span><span class=\"n\">bucket<\/span><span class=\"p\">,<\/span>\n      <span class=\"ss\">key: <\/span><span class=\"n\">key<\/span>\n    <span class=\"p\">)<\/span>\n\n    <span class=\"n\">tempfile<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>With <code class=\"language-plaintext highlighter-rouge\">Aws::ClientStubs<\/code>, it\u2019s possible to test it like this (using RSpec):<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">require<\/span> <span class=\"s1\">'rspec'<\/span>\n\n<span class=\"n\">describe<\/span> <span class=\"no\">S3Downloader<\/span> <span class=\"k\">do<\/span>\n  <span class=\"n\">describe<\/span> <span class=\"s1\">'#download'<\/span> <span class=\"k\">do<\/span>\n    <span class=\"n\">it<\/span> <span class=\"s1\">'fetches the S3 object to a tempfile'<\/span> <span class=\"k\">do<\/span>\n      <span class=\"c1\"># setup<\/span>\n      <span class=\"n\">s3_client<\/span> <span class=\"o\">=<\/span> <span class=\"no\">Aws<\/span><span class=\"o\">::<\/span><span class=\"no\">S3<\/span><span class=\"o\">::<\/span><span class=\"no\">Client<\/span><span class=\"p\">.<\/span><span class=\"nf\">new<\/span><span class=\"p\">(<\/span><span class=\"ss\">stub_responses: <\/span><span class=\"kp\">true<\/span><span class=\"p\">)<\/span> <span class=\"c1\"># &lt;---- look here!<\/span>\n\n      <span class=\"n\">file_body<\/span> <span class=\"o\">=<\/span> <span class=\"s1\">'test'<\/span>\n      <span class=\"n\">s3_client<\/span><span class=\"p\">.<\/span><span class=\"nf\">stub_responses<\/span><span class=\"p\">(<\/span><span class=\"ss\">:get_object<\/span><span class=\"p\">,<\/span> <span class=\"ss\">body: <\/span><span class=\"n\">file_body<\/span><span class=\"p\">)<\/span> <span class=\"c1\"># &lt;--- and here<\/span>\n\n      <span class=\"n\">expect<\/span><span class=\"p\">(<\/span><span class=\"n\">s3_client<\/span><span class=\"p\">).<\/span><span class=\"nf\">to<\/span> <span class=\"n\">receive<\/span><span class=\"p\">(<\/span><span class=\"ss\">:get_object<\/span><span class=\"p\">).<\/span><span class=\"nf\">and_call_original<\/span>\n\n      <span class=\"c1\"># exercise<\/span>\n      <span class=\"n\">result<\/span> <span class=\"o\">=<\/span> <span class=\"n\">described_class<\/span>\n        <span class=\"p\">.<\/span><span class=\"nf\">new<\/span><span class=\"p\">(<\/span><span class=\"ss\">s3_client: <\/span><span class=\"n\">s3_client<\/span><span class=\"p\">)<\/span>\n        <span class=\"p\">.<\/span><span class=\"nf\">download<\/span><span class=\"p\">(<\/span><span class=\"ss\">key: <\/span><span class=\"s1\">'any'<\/span><span class=\"p\">,<\/span> <span class=\"ss\">bucket: <\/span><span class=\"s1\">'bucket'<\/span><span class=\"p\">)<\/span>\n\n      <span class=\"c1\"># verify<\/span>\n      <span class=\"n\">expect<\/span><span class=\"p\">(<\/span><span class=\"n\">result<\/span><span class=\"p\">.<\/span><span class=\"nf\">read<\/span><span class=\"p\">).<\/span><span class=\"nf\">to<\/span> <span class=\"n\">eq<\/span><span class=\"p\">(<\/span><span class=\"n\">file_body<\/span><span class=\"p\">)<\/span>\n    <span class=\"k\">end<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<h2 id=\"how-would-i-test-it-without-awsclientstubs\">How would I test it without Aws::ClientStubs<\/h2>\n\n<p>Without <code class=\"language-plaintext highlighter-rouge\">Aws::ClientStubs<\/code>, it would be necessary to choose one of the following approaches to test it:<\/p>\n\n<h3 id=\"1-using-a-valid-s3-credential-to-download-a-file-from-s3-every-time-you-run-the-test\">1. Using a valid S3 credential to download a file from S3 every time you run the test<\/h3>\n\n<p>Pros:<\/p>\n<ul>\n  <li>It\u2019s the easiest approach, because the test would use the same code as the production code.<\/li>\n  <li>No mocks and stubs are necessary<\/li>\n  <li>No VCR setup is necessary<\/li>\n  <li>It\u2019s a reliable test since it\u2019s integrated with the real API<\/li>\n<\/ul>\n\n<p>Cons:<\/p>\n<ul>\n  <li>The test would only pass if connected to the Internet<\/li>\n  <li>The test would depend on the state of the bucket and on the availability of S3<\/li>\n  <li>The test would be slow to run<\/li>\n<\/ul>\n\n<h3 id=\"2-using-the-gem-vcr\">2. Using the gem <a href=\"https:\/\/github.com\/vcr\/vcr\">VCR<\/a><\/h3>\n\n<p>Pros:<\/p>\n<ul>\n  <li>It would be possible to run the tests offline<\/li>\n  <li>The test would run quickly<\/li>\n  <li>It\u2019s a reliable-ish test since it\u2019s integrating with the real API when recording the cassette<\/li>\n<\/ul>\n\n<p>Cons:<\/p>\n<ul>\n  <li>To record a VCR cassette, it would be necessary a valid S3 credential and being connected to the Internet<\/li>\n  <li>The test setup and tear down would become a bit more complex, because it would have to ensure there is a file on the S3 bucket to be downloaded, when recording the cassette<\/li>\n  <li>If the API changes its interface, the test may not notice it, since it would be using an older recorded cassette version of it<\/li>\n  <li>The test may fail if the payload or query string changes. This would be good to acknowledge that something has changed, sending data to the API, but it would be bad because it may be hard to fix it, and it can result in flaky tests<\/li>\n<\/ul>\n\n<h3 id=\"3-using-a-mockstub\">3. Using a mock\/stub<\/h3>\n\n<p>Pros:<\/p>\n<ul>\n  <li>It would be possible to run the tests offline<\/li>\n  <li>The test would run quickly<\/li>\n  <li>It wouldn\u2019t be necessary to have a valid S3 credential to run the test<\/li>\n  <li>The setup and tear down wouldn\u2019t require uploading or deleting the file from S3<\/li>\n<\/ul>\n\n<p>Cons:<\/p>\n<ul>\n  <li>A test with stubs and mocks, when done inaccurately, could be testing nothing, example:<\/li>\n<\/ul>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">describe<\/span> <span class=\"no\">S3Downloader<\/span> <span class=\"k\">do<\/span>\n  <span class=\"n\">describe<\/span> <span class=\"s1\">'#download'<\/span> <span class=\"k\">do<\/span>\n    <span class=\"n\">it<\/span> <span class=\"s1\">'fetches the S3 object to a tempfile'<\/span> <span class=\"k\">do<\/span>\n      <span class=\"c1\"># setup<\/span>\n      <span class=\"n\">s3_client<\/span> <span class=\"o\">=<\/span> <span class=\"no\">Aws<\/span><span class=\"o\">::<\/span><span class=\"no\">S3<\/span><span class=\"o\">::<\/span><span class=\"no\">Client<\/span><span class=\"p\">.<\/span><span class=\"nf\">new<\/span>\n\n      <span class=\"n\">allow<\/span><span class=\"p\">(<\/span><span class=\"n\">s3_client<\/span><span class=\"p\">).<\/span><span class=\"nf\">to<\/span> <span class=\"n\">receive<\/span><span class=\"p\">(<\/span><span class=\"ss\">:get_object<\/span><span class=\"p\">).<\/span><span class=\"nf\">and_return<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"object\"<\/span><span class=\"p\">)<\/span> <span class=\"c1\"># &lt;-- this is not so good<\/span>\n\n      <span class=\"c1\"># verify<\/span>\n      <span class=\"n\">expect<\/span><span class=\"p\">(<\/span><span class=\"n\">s3_client<\/span><span class=\"p\">.<\/span><span class=\"nf\">get_object<\/span><span class=\"p\">).<\/span><span class=\"nf\">to<\/span> <span class=\"n\">eq<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"object\"<\/span><span class=\"p\">)<\/span>\n    <span class=\"k\">end<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>In this case, there is no guarantee that the return of <code class=\"language-plaintext highlighter-rouge\">Aws::S3::Client#get_object<\/code> is the same as the stubbed one.<\/p>\n\n<p>Even if we compare and we are sure that it is correct, if the API or method change their response, the test would be stuck with the stubbed response and this could be noticed only in production!<\/p>\n\n<h2 id=\"why-i-like-the-approach-of-awsclientstubs\">Why I like the approach of Aws::ClientStubs<\/h2>\n\n<p>When we use a client of an external API and it has a test class\/helper, we believe that we can trust on its stubbed responses.<\/p>\n\n<p>If the API response changes, we expect that the new version of the gem updates also the response of the <code class=\"language-plaintext highlighter-rouge\">ClientStub<\/code>.<\/p>\n\n<h2 id=\"how-to-implement-a-client-stub-in-your-client-gem\">How to implement a Client Stub in your client gem<\/h2>\n\n<p>Suppose your gem has a method to retrieve a list of orders:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">client<\/span> <span class=\"o\">=<\/span> <span class=\"no\">YourClient<\/span><span class=\"p\">.<\/span><span class=\"nf\">new<\/span>\n<span class=\"n\">client<\/span><span class=\"p\">.<\/span><span class=\"nf\">list_orders<\/span>\n<span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"s2\">\"orders\"<\/span> <span class=\"p\">:<\/span> <span class=\"p\">[<\/span>\n    <span class=\"p\">{<\/span>\n      <span class=\"s2\">\"id\"<\/span><span class=\"p\">:<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span>\n      <span class=\"s2\">\"sku\"<\/span><span class=\"p\">:<\/span> <span class=\"s2\">\"XYZ12\"<\/span><span class=\"p\">,<\/span>\n      <span class=\"s2\">\"quantity\"<\/span><span class=\"p\">:<\/span> <span class=\"mi\">3<\/span><span class=\"p\">,<\/span>\n      <span class=\"s2\">\"customer_id\"<\/span><span class=\"p\">:<\/span> <span class=\"mi\">55<\/span>\n    <span class=\"p\">},<\/span>\n    <span class=\"p\">{<\/span>\n      <span class=\"s2\">\"id\"<\/span><span class=\"p\">:<\/span> <span class=\"mi\">2<\/span><span class=\"p\">,<\/span>\n      <span class=\"s2\">\"sku\"<\/span><span class=\"p\">:<\/span> <span class=\"s2\">\"WZA32\"<\/span><span class=\"p\">,<\/span>\n      <span class=\"s2\">\"quantity\"<\/span><span class=\"p\">:<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span>\n      <span class=\"s2\">\"customer_id\"<\/span><span class=\"p\">:<\/span> <span class=\"mi\">44<\/span>\n    <span class=\"p\">},<\/span>\n  <span class=\"p\">]<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Then, you can add an option to stub the response:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">client<\/span> <span class=\"o\">=<\/span> <span class=\"no\">YourClient<\/span><span class=\"p\">.<\/span><span class=\"nf\">new<\/span><span class=\"p\">(<\/span><span class=\"ss\">stub_response: <\/span><span class=\"kp\">true<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">orders<\/span> <span class=\"o\">=<\/span> <span class=\"n\">client<\/span><span class=\"p\">.<\/span><span class=\"nf\">list_orders<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This is a simple way to implement it on the gem side:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class<\/span> <span class=\"nc\">YourClient<\/span>\n  <span class=\"k\">def<\/span> <span class=\"nf\">initialize<\/span><span class=\"p\">(<\/span><span class=\"ss\">stub_response: <\/span><span class=\"kp\">false<\/span><span class=\"p\">)<\/span>\n    <span class=\"vi\">@stub_response<\/span> <span class=\"o\">=<\/span> <span class=\"n\">stub_response<\/span>\n  <span class=\"k\">end<\/span>\n\n  <span class=\"k\">def<\/span> <span class=\"nf\">list_orders<\/span>\n    <span class=\"k\">return<\/span> <span class=\"no\">YourClientStubbed<\/span><span class=\"p\">.<\/span><span class=\"nf\">new<\/span><span class=\"p\">.<\/span><span class=\"nf\">list_orders<\/span> <span class=\"k\">if<\/span> <span class=\"vi\">@stub_response<\/span>\n\n    <span class=\"c1\">## the real implementation<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n\n<span class=\"k\">class<\/span> <span class=\"nc\">YourClientStubbed<\/span>\n  <span class=\"k\">def<\/span> <span class=\"nf\">list_orders<\/span>\n    <span class=\"p\">{<\/span>\n      <span class=\"s2\">\"orders\"<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">[<\/span>\n        <span class=\"p\">{<\/span>\n          <span class=\"s2\">\"id\"<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span>\n          <span class=\"s2\">\"sku\"<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"s2\">\"SKU1\"<\/span><span class=\"p\">,<\/span>\n          <span class=\"s2\">\"quantity\"<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span>\n          <span class=\"s2\">\"customer_id\"<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"mi\">1<\/span>\n        <span class=\"p\">},<\/span>\n        <span class=\"p\">{<\/span>\n          <span class=\"s2\">\"id\"<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"mi\">2<\/span><span class=\"p\">,<\/span>\n          <span class=\"s2\">\"sku\"<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"s2\">\"SKU2\"<\/span><span class=\"p\">,<\/span>\n          <span class=\"s2\">\"quantity\"<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"mi\">2<\/span><span class=\"p\">,<\/span>\n          <span class=\"s2\">\"customer_id\"<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"mi\">2<\/span>\n        <span class=\"p\">},<\/span>\n      <span class=\"p\">]<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>To ensure that your stubbed method returns the same content as the original one, it\u2019s possible to create a test as the following:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">require<\/span> <span class=\"s1\">'rspec'<\/span>\n\n<span class=\"n\">describe<\/span> <span class=\"no\">YourClientStubbed<\/span> <span class=\"k\">do<\/span>\n  <span class=\"k\">def<\/span> <span class=\"nf\">create_order<\/span><span class=\"p\">(<\/span><span class=\"o\">**<\/span><span class=\"n\">args<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">post<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"\/orders\"<\/span><span class=\"p\">,<\/span> <span class=\"n\">args<\/span><span class=\"p\">)<\/span>\n  <span class=\"k\">end<\/span>\n\n  <span class=\"n\">describe<\/span> <span class=\"s2\">\"#list_orders\"<\/span> <span class=\"k\">do<\/span>\n    <span class=\"n\">it<\/span> <span class=\"s2\">\"returns the same structure as the real api\"<\/span><span class=\"p\">,<\/span> <span class=\"ss\">:vcr<\/span> <span class=\"k\">do<\/span>\n      <span class=\"n\">stubbed_orders<\/span> <span class=\"o\">=<\/span> <span class=\"no\">YourClientStubbed<\/span><span class=\"p\">.<\/span><span class=\"nf\">new<\/span><span class=\"p\">.<\/span><span class=\"nf\">list_orders<\/span>\n\n      <span class=\"n\">stubbed_orders<\/span><span class=\"p\">.<\/span><span class=\"nf\">each<\/span> <span class=\"k\">do<\/span> <span class=\"o\">|<\/span><span class=\"n\">stubbed_order<\/span><span class=\"o\">|<\/span>\n        <span class=\"n\">create_order<\/span><span class=\"p\">(<\/span>\n          <span class=\"ss\">id: <\/span><span class=\"n\">stubbed_order<\/span><span class=\"p\">.<\/span><span class=\"nf\">id<\/span><span class=\"p\">,<\/span>\n          <span class=\"ss\">sku: <\/span><span class=\"n\">stubbed_order<\/span><span class=\"p\">.<\/span><span class=\"nf\">sku<\/span><span class=\"p\">,<\/span>\n          <span class=\"ss\">quantity: <\/span><span class=\"n\">stubbed_order<\/span><span class=\"p\">.<\/span><span class=\"nf\">quantity<\/span><span class=\"p\">,<\/span>\n          <span class=\"ss\">customer_id: <\/span><span class=\"n\">stubbed_order<\/span><span class=\"p\">.<\/span><span class=\"nf\">customer_id<\/span>\n        <span class=\"p\">)<\/span>\n      <span class=\"k\">end<\/span>\n\n      <span class=\"n\">real_orders<\/span> <span class=\"o\">=<\/span> <span class=\"no\">YourClient<\/span><span class=\"p\">.<\/span><span class=\"nf\">new<\/span><span class=\"p\">.<\/span><span class=\"nf\">list_orders<\/span>\n\n      <span class=\"n\">expect<\/span><span class=\"p\">(<\/span><span class=\"n\">stubbed_orders<\/span><span class=\"p\">).<\/span><span class=\"nf\">to<\/span> <span class=\"n\">eq<\/span><span class=\"p\">(<\/span><span class=\"n\">real_orders<\/span><span class=\"p\">)<\/span>\n    <span class=\"k\">end<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>With that, when a new attribute is added to the API, the test would break.<\/p>\n\n<p>For this test, I would recommend using VCR to test it against the real API, because it must be a strong test to ensure the stubbed response is valid!<\/p>\n\n<p>I wrote another article with more details of VCR gem usage <a href=\"\/10_tips_to_help_using_the_VCR_gem_in_your_ruby_test_suite.html\">here<\/a>.<\/p>\n\n<h2 id=\"a-simple-comparison-of-each-approach\">A simple comparison of each approach<\/h2>\n\n<p>The following table summarizes the pros and cons of each approach:<\/p>\n\n<table>\n  <thead>\n    <tr>\n      <th>Criteria<\/th>\n      <th>No mocks\/stubs, No VCR<\/th>\n      <th>VCR<\/th>\n      <th>Mocks\/stubs<\/th>\n      <th>Client stub<\/th>\n    <\/tr>\n  <\/thead>\n  <tbody>\n    <tr>\n      <td>Easy?<\/td>\n      <td>\u2705<\/td>\n      <td>\ud83d\udeab<\/td>\n      <td>\ud83d\udc81\u200d\u2642\ufe0f<\/td>\n      <td>\u2705<\/td>\n    <\/tr>\n    <tr>\n      <td>Works offline ?<\/td>\n      <td>\ud83d\udeab<\/td>\n      <td>\u2705<\/td>\n      <td>\u2705<\/td>\n      <td>\u2705<\/td>\n    <\/tr>\n    <tr>\n      <td>Free of S3 state?<\/td>\n      <td>\ud83d\udeab<\/td>\n      <td>\ud83d\udc81\u200d\u2642\ufe0f<\/td>\n      <td>\u2705<\/td>\n      <td>\u2705<\/td>\n    <\/tr>\n    <tr>\n      <td>Free of S3 availability?<\/td>\n      <td>\ud83d\udeab<\/td>\n      <td>\ud83d\udc81\u200d<\/td>\n      <td>\u2705<\/td>\n      <td>\u2705<\/td>\n    <\/tr>\n    <tr>\n      <td>Fast?<\/td>\n      <td>\ud83d\udeab<\/td>\n      <td>\u2705<\/td>\n      <td>\u2705<\/td>\n      <td>\u2705<\/td>\n    <\/tr>\n    <tr>\n      <td>Testing for real?<\/td>\n      <td>\u2705<\/td>\n      <td>\ud83d\udc81\u200d<\/td>\n      <td>\ud83d\udeab<\/td>\n      <td>\u2705<\/td>\n    <\/tr>\n  <\/tbody>\n<\/table>\n\n<h2 id=\"conclusion\">Conclusion<\/h2>\n\n<p>When you use a client of an external API, such as <a href=\"https:\/\/aws.amazon.com\/sdk-for-ruby\/\">AWS Ruby SDK<\/a>, take a look on its docs to find out if there is something similar to a <code class=\"language-plaintext highlighter-rouge\">ClientStub<\/code> and use it in your tests!<\/p>\n\n<p>If you are a contributor of an API client gem, think about adding a <code class=\"language-plaintext highlighter-rouge\">ClientStub<\/code> to help the users to create their tests!<\/p>\n\n<p>Thanks @leandro_gs for reviewing it!<\/p>","author":{"name":{}},"summary":"A while ago, I used AWS Ruby SDK to create a method to download files from S3, and I was introduced to Aws::ClientStubs, which is amazing, and it opened up my mind on how to test external APIs with a more integrated approach!"},{"title":"An index route without pagination can destroy your app!","link":{"@attributes":{"href":"https:\/\/fabioperrella.github.io\/2020\/03\/30\/an-index-route-without-pagination-can-destroy-your-app.html","rel":"alternate","type":"text\/html","title":"An index route without pagination can destroy your app!"}},"published":"2020-03-30T00:00:00+00:00","updated":"2020-03-30T00:00:00+00:00","id":"https:\/\/fabioperrella.github.io\/2020\/03\/30\/an-index-route-without-pagination-can-destroy-your-app","content":"<p>Last week I was involved in a problem that started to happen with an internal\napplication.<\/p>\n\n<p>This app is written with Ruby on Rails and it uses <a href=\"https:\/\/puma.io\/\">Puma<\/a> as\nthe web server. It has an api which is used by a lot of other important\napplications.<\/p>\n\n<p>Suddenly, it started to consume a lot of memory and it was killing the Puma\nprocess every 20 minutes in all web servers (there are 3 in production under a\nload balancer).<\/p>\n\n<p><img src=\"\/images\/pagination_1.png\" alt=\"memory chart\" \/><\/p>\n\n<p>We\u2019ve noticed the problem started on 03\/23 16:00, so we looked all the changes\nthat have been made at this time and it seemed that none of them was responsible\nfor it.<\/p>\n\n<p>We tried to revert some of them, but the problem persisted!<\/p>\n\n<p>We created a script to log every 2 seconds how was the memory usage in the server.<\/p>\n\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\">#!\/bin\/bash<\/span>\n\n<span class=\"k\">while<\/span> :<span class=\"p\">;<\/span> <span class=\"k\">do\n        <\/span><span class=\"nv\">mem_used<\/span><span class=\"o\">=<\/span><span class=\"s2\">\"<\/span><span class=\"si\">$(<\/span>free <span class=\"nt\">-m<\/span> | <span class=\"nb\">grep <\/span>Mem | <span class=\"nb\">awk<\/span> <span class=\"s1\">'{print$3}'<\/span><span class=\"si\">)<\/span><span class=\"s2\">\"<\/span>\n        <span class=\"nv\">mem_free<\/span><span class=\"o\">=<\/span><span class=\"s2\">\"<\/span><span class=\"si\">$(<\/span>free <span class=\"nt\">-m<\/span> | <span class=\"nb\">grep <\/span>Mem | <span class=\"nb\">awk<\/span> <span class=\"s1\">'{print$4}'<\/span><span class=\"si\">)<\/span><span class=\"s2\">\"<\/span>\n        <span class=\"nv\">mem_avail<\/span><span class=\"o\">=<\/span><span class=\"s2\">\"<\/span><span class=\"si\">$(<\/span>free <span class=\"nt\">-m<\/span> | <span class=\"nb\">grep <\/span>Mem | <span class=\"nb\">awk<\/span> <span class=\"s1\">'{print$4}'<\/span><span class=\"si\">)<\/span><span class=\"s2\">\"<\/span>\n        logger <span class=\"nt\">-t<\/span> MemWatcher <span class=\"s2\">\"Used: <\/span><span class=\"k\">${<\/span><span class=\"nv\">mem_used<\/span><span class=\"k\">}<\/span><span class=\"s2\">. Free: <\/span><span class=\"k\">${<\/span><span class=\"nv\">mem_free<\/span><span class=\"k\">}<\/span><span class=\"s2\">. Available: <\/span><span class=\"k\">${<\/span><span class=\"nv\">mem_avail<\/span><span class=\"k\">}<\/span><span class=\"s2\">.\"<\/span><span class=\"p\">;<\/span>\n        <span class=\"nb\">sleep <\/span>2<span class=\"p\">;<\/span> <span class=\"k\">done<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The idea was trying to isolate some requests and try to find which request was\nresponsible for it.<\/p>\n\n<p>I used some <code class=\"language-plaintext highlighter-rouge\">grep<\/code> and <code class=\"language-plaintext highlighter-rouge\">awk<\/code> to filter the log the way that I wanted but it wasn\u2019t\nclear which request was causing the problem.<\/p>\n\n<p>I\u2019ve also searched how to investigate memory leak problems in Ruby apps and I found\ngreat posts about it:<\/p>\n\n<ul>\n  <li><a href=\"http:\/\/www.be9.io\/2015\/09\/21\/memory-leak\/\">http:\/\/www.be9.io\/2015\/09\/21\/memory-leak\/<\/a><\/li>\n  <li><a href=\"https:\/\/www.spacevatican.org\/2019\/5\/4\/debugging-a-memory-leak-in-a-rails-app\/\">https:\/\/www.spacevatican.org\/2019\/5\/4\/debugging-a-memory-leak-in-a-rails-app\/<\/a><\/li>\n  <li><a href=\"https:\/\/rollout.io\/blog\/debugging-a-memory-leak-on-heroku\/\">https:\/\/rollout.io\/blog\/debugging-a-memory-leak-on-heroku\/<\/a><\/li>\n<\/ul>\n\n<p>But I chose to use this approach only in the last case, because it is not so\nsimple to do it.<\/p>\n\n<p>In the meantime, we did another test. When the memory started to grow up, we\u2019ve\nremoved the machine from the load balancer. We expected the memory to stop\nincreasing, but it didn\u2019t! It seemed that a request was being processed for a\nlong time.<\/p>\n\n<p>At this time, it was very late, I was very tired and we stopped the investigation.<\/p>\n\n<p>In the next day, I asked other developer to help me. He is a very experienced\nguy, maybe he could bring some new ideas to the investigation.<\/p>\n\n<p>He told me to look the requests which were failing in Nginx error log, maybe we could\nfind a timeout or something similar there.<\/p>\n\n<p>We use Nginx as a reverse proxy in front of the Puma web server and I have some\nexperience finding the answer to the problems in this log as written in my <a href=\"\/2020\/02\/24\/investigating_api_timeout_errors_when_using_nginx_and_puma.html\">last\npost<\/a>.<\/p>\n\n<p>Looking at this log, we started to remove things that was irrelevant, and then\nI\u2019ve noticed a very strange URL being called:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>GET https:\/\/appname.com\/features?id\n<\/code><\/pre><\/div><\/div>\n\n<p>Then all the pieces joined into my mind!<\/p>\n\n<p>When the problem started, a change was made in another application which could\nhave introduced a bug like this, to find a feature without a filter, which would\nreturn <strong>all the features<\/strong>!! And there were 5.6 million of records in this table\nat this time!<\/p>\n\n<p>The application with this problem uses <a href=\"https:\/\/puma.io\">Puma<\/a> as the webserver.<\/p>\n\n<p>I discovered that Puma does not have and maybe\n<a href=\"https:\/\/github.com\/puma\/puma\/issues\/1774\">it will never have a timeout configuration<\/a>,\nso a request can last forever to return.<\/p>\n\n<p>In this case, the app was retrieving 5.6 million records from the database and\nbuilding a huge JSON to render!<\/p>\n\n<p>Now I will discuss what we could do to prevent this, some ideas to avoid it\nin the future and some tips that we used to find the root cause.<\/p>\n\n<h2 id=\"0-metrics\">0. Metrics<\/h2>\n\n<p>It\u2019s <strong>very important<\/strong> to have a dashboard with metrics where you find when the\nproblem has started!<\/p>\n\n<p>In this case, we have <a href=\"https:\/\/grafana.com\/\">Grafana<\/a>,\n<a href=\"https:\/\/www.influxdata.com\/\">Influxdb<\/a> and\n<a href=\"https:\/\/www.influxdata.com\/time-series-platform\/telegraf\/\">Telegraf<\/a> reporting the basic metrics.<\/p>\n\n<p>Some metrics that helped me in this process:<\/p>\n<ul>\n  <li>memory usage<\/li>\n  <li>load average<\/li>\n  <li>cpu<\/li>\n<\/ul>\n\n<h2 id=\"1-it-is-good-to-know-a-few-bash-commands-and-vim\">1. It is good to know a few Bash commands and Vim<\/h2>\n\n<p>Analyzing the logs is a tough task because there is a lot of information!<\/p>\n\n<p>I used a lot of <code class=\"language-plaintext highlighter-rouge\">grep<\/code> and <code class=\"language-plaintext highlighter-rouge\">awk<\/code> to format things in a way that I wanted.\nSo I suggest everybody to learn using these commands.<\/p>\n\n<p>This is an example of a command that I used to show only the columns that I wanted\nfrom the log:<\/p>\n\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nv\">$ <\/span><span class=\"nb\">grep <\/span>app_name \/var\/log\/syslog | <span class=\"nb\">awk<\/span> <span class=\"s1\">'{print $3, $20, $23, $24, $31}'<\/span> <span class=\"o\">&gt;<\/span> \/tmp\/requests\n<\/code><\/pre><\/div><\/div>\n\n<p>Vim is not my default text editor, but I know the basic commands and this is very\nimportant, because I can\u2019t use Sublime Text in the server.<\/p>\n\n<h2 id=\"2-pagination\">2. Pagination<\/h2>\n\n<p>This route <code class=\"language-plaintext highlighter-rouge\">\/features<\/code> does not have a pagination mechanism. So if someone\ntries to get all the features from the database, the application would process\nit!<\/p>\n\n<p>If we had pagination, with a default value for the page size, this problem maybe\nnever has happened!<\/p>\n\n<p>But even with the default page size, someone could use a very high value and\ncauses the same problem. So I think the page size must be limited!<\/p>\n\n<p>For now, before implementing the pagination, we will implement a validation to\navoid using this route without filters. Actually, there is a validation like this\nbut it was accepting parameters with null values, so we will change it.<\/p>\n\n<h2 id=\"3-puma-timeout\">3. Puma timeout<\/h2>\n\n<p>I didn\u2019t know that\n<a href=\"https:\/\/github.com\/puma\/puma\/issues\/1774\">Puma does not have and maybe it will never have a timeout configuration<\/a>,\n and now that I know it, I think we must enforce the application to avoid problems\n like this.<\/p>\n\n<p>There are some issues suggesting the usage of the gem rack-timeout to deal with\nit but I found a <a href=\"https:\/\/github.com\/sharpstone\/rack-timeout\/blob\/master\/doc\/risks.md\">list of risks of using this approach<\/a>\nand I don\u2019t recommend it.<\/p>\n\n<p>Another option would be using <a href=\"https:\/\/yhbt.net\/unicorn\/\">Unicorn<\/a>, which has a\n<a href=\"https:\/\/github.com\/defunkt\/unicorn\/blob\/master\/lib\/unicorn\/configurator.rb#L243\">timeout configuration<\/a>,\nbut Unicorn uses a process for each worker and because of this, it would require\nmore memory to deal with the same amount of requests.<\/p>\n\n<p>In our case, the app was using only a few threads, and it would use\nalmost the same amount of memory switching to Unicorn. But if it was using a lot\nof threads per worker, it would require much more memory to deal with it.<\/p>\n\n<p>Another relevant point when choosing between Puma and Unicorn is to know if your\napplication is thread-safe, because Puma required it when using threads.<\/p>\n\n<p>It is very difficult to ensure an application is thread-safe, because you also\nmust ensure that your code and all the dependencies are thread-safe. There are\nmany posts about it, for example, this <a href=\"https:\/\/bearmetal.eu\/theden\/how-do-i-know-whether-my-rails-app-is-thread-safe-or-not\/\">https:\/\/bearmetal.eu\/theden\/how-do-i-know-whether-my-rails-app-is-thread-safe-or-not\/<\/a><\/p>\n\n<h2 id=\"4-using-a-static-type-checker\">4. Using a static type checker<\/h2>\n\n<p>The application which introduced the bug has a code like this:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class<\/span> <span class=\"nc\">FeatureRepository<\/span>\n  <span class=\"k\">def<\/span> <span class=\"nc\">self<\/span><span class=\"o\">.<\/span><span class=\"nf\">find<\/span><span class=\"p\">(<\/span><span class=\"nb\">id<\/span><span class=\"p\">)<\/span>\n    <span class=\"no\">OtherApp<\/span><span class=\"o\">::<\/span><span class=\"no\">Clients<\/span><span class=\"o\">::<\/span><span class=\"no\">V1<\/span><span class=\"o\">::<\/span><span class=\"no\">Feature<\/span><span class=\"p\">.<\/span><span class=\"nf\">find<\/span><span class=\"p\">(<\/span><span class=\"nb\">id<\/span><span class=\"p\">)<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>With this code, if someone passes <code class=\"language-plaintext highlighter-rouge\">id=nil<\/code>, it would trigger the request that I\u2019ve\nmentioned before.<\/p>\n\n<p>If it was using a static type checker like <a href=\"https:\/\/sorbet.org\/\">Sorbet<\/a>, it\nwould be possible to enforce that the <code class=\"language-plaintext highlighter-rouge\">id<\/code> parameter must be an integer and it\ndoes not accept <code class=\"language-plaintext highlighter-rouge\">nil<\/code>, but we are not using something like this, unfortunately :(<\/p>\n\n<p>I\u2019ve tried to add Sorbet to this project, but I couldn\u2019t. It is a legacy application,\nusing Sinatra in a strange way, so it raised a lot of errors when running\n<code class=\"language-plaintext highlighter-rouge\">srb init<\/code>.<\/p>\n\n<h2 id=\"5-rollback-changes-that-are-suspicious\">5. Rollback changes that are suspicious<\/h2>\n\n<p>When I started to look at the changes that happened at the time the problem started,\nI thought that this change, which caused the problem, was not responsible for it.<\/p>\n\n<p>If I tried to rollback it, I would have found the problem much faster.<\/p>\n\n<p>So next time, I will remember to revert all the suspicious changes!<\/p>\n\n<h2 id=\"6-ask-help-to-different-teammates\">6. Ask help to different teammates<\/h2>\n\n<p>When you are stuck in a problem and doesn\u2019t know how to proceed, ask help for\npeople that are not involved in the problem.<\/p>\n\n<p>You will be forced to explain all the details, and sometimes it can show you\nwhat the problem is (like the <a href=\"https:\/\/rubberduckdebugging.com\/\">Rubber Duck Debugging<\/a>), but in\nother cases, it can show you different paths to find or solve the problem!<\/p>\n\n<hr \/>\n\n<p>Thanks <a href=\"https:\/\/github.com\/zinho\">Rafael Carvalho<\/a> for the review!<\/p>","author":{"name":{}},"summary":"Last week I was involved in a problem that started to happen with an internal application."},{"title":"Investigating API timeout errors (when using Nginx and Puma)","link":{"@attributes":{"href":"https:\/\/fabioperrella.github.io\/2020\/02\/24\/investigating_api_timeout_errors_when_using_nginx_and_puma.html","rel":"alternate","type":"text\/html","title":"Investigating API timeout errors (when using Nginx and Puma)"}},"published":"2020-02-24T00:00:00+00:00","updated":"2020-02-24T00:00:00+00:00","id":"https:\/\/fabioperrella.github.io\/2020\/02\/24\/investigating_api_timeout_errors_when_using_nginx_and_puma","content":"<p>On Ruby on Rails applications, a very common pattern is to <a href=\"http:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html\">use Nginx as the\nreverse proxy<\/a> and\n<a href=\"https:\/\/puma.io\/\">Puma<\/a> as the application server.<\/p>\n\n<p>One day, I was investigating a problem reported by the support team that\nsometimes a page was not being rendered correctly, showing an unexpected\nerror.<\/p>\n\n<p>This page gets the data from an API of another application and it seemed this\nAPI was having some problem.<\/p>\n\n<p>First, I tried to see the most recent API errors on <a href=\"https:\/\/sentry.io\/\">Sentry<\/a> which\nis the error monitoring tool that we use and I couldn\u2019t find anything relevant.<\/p>\n\n<p>We also use <a href=\"https:\/\/newrelic.com\">Newrelic<\/a>, which provides a lot of information\nabout the errors and I couldn\u2019t find anything too.<\/p>\n\n<p>Then I impersonated into a customer account where I could reproduce the error.<\/p>\n\n<p>I noticed the request was taking too much to complete, so it seemed a timeout\nin somewhere, but I was curious why I wasn\u2019t finding errors related to it in\nthe API\u2019s Sentry page.<\/p>\n\n<p>I remembered that Nginx has its access and error logs, so I looked to see what was\ngoing on.<\/p>\n\n<p>When I opened the error log, I could find all the errors that I was searching for!<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>...\n2020\/01\/30 14:05:24 : upstream timed out (110: Connection timed out) while reading response header from upstream, client: 10.32.32.248, server: ~^app-name\\..*\\.company\\.com\\.br$, request: \"POST \/some_route\/0000000066410219 HTTP\/1.1\", upstream: \"http:\/\/unix:\/var\/www\/app-name\/tmp\/sockets\/app-name.sock\/some_route\/0000000066410219\"\n2020\/01\/30 14:05:27 : upstream timed out (110: Connection timed out) while reading response header from upstream, client: 10.32.32.249, server: ~^app-name\\..*\\.company\\.com\\.br$, request: \"POST \/some_route\/0000000066410862 HTTP\/1.1\", upstream: \"http:\/\/unix:\/var\/www\/app-name\/tmp\/sockets\/app-name.sock\/some_route\/0000000066410862\"\n...\n<\/code><\/pre><\/div><\/div>\n\n<p>Then I understood the problem! The request to Puma was timing out on Nginx, but\n<strong>it wasn\u2019t an error on the application and this is why I couldn\u2019t find anything\non Sentry<\/strong>!<\/p>\n\n<p>In the next days, we worked to improve\/limit the API\u2019s response time (the problem\nwas in another API which was slow and we chose to set a smaller timeout for it)\nand I after the deploy it, I started to check the error logs every day to see if\nit had improved.<\/p>\n\n<p>But at this point, when I stopped to check the logs, we could have the same\nproblem again and no one would notice before a customer reports the problem.<\/p>\n\n<p>Actually we didn\u2019t have time to improve it and we are in the same situation\nuntil today.<\/p>\n\n<p>I chose to write this post to:<\/p>\n\n<ul>\n  <li>expose the problem<\/li>\n  <li>get feedback on how the people deal with these problems<\/li>\n<\/ul>\n\n<p>I have an idea of how improving the observability, which is to measure the\nresponse time on the API and\n<a href=\"https:\/\/docs.sentry.io\/clients\/ruby\/#reporting-failures\">report the failure to Sentry<\/a>\nif it is longer than the expected.<\/p>\n\n<p>I would prefer to send it to Sentry because it sends us a notification of every\nnew error that is happening on the application, and I\u2019m used to reading these\nemails to see if there is something new going on.<\/p>\n\n<p>So my tip is to check the Nginx logs, it can give us some important\ninformation!<\/p>","author":{"name":{}},"summary":"On Ruby on Rails applications, a very common pattern is to use Nginx as the reverse proxy and Puma as the application server."},{"title":"Agility in software development - Text Editor","link":{"@attributes":{"href":"https:\/\/fabioperrella.github.io\/20191013_agility_in_software_development-text_editor.html","rel":"alternate","type":"text\/html","title":"Agility in software development - Text Editor"}},"published":"2019-10-13T00:00:00+00:00","updated":"2019-10-13T00:00:00+00:00","id":"https:\/\/fabioperrella.github.io\/agility_in_software_development-text_editor","content":"<p>I\u2019ve been programming for a while and every day I try to learn something new to\nbe more effective and fast.<\/p>\n\n<p>I think every developer should care about it to improve their skills and lose\nless time with repetitive and slow things.<\/p>\n\n<p>When I\u2019m pairing, people use to ask me how I do some things, what tools I use,\nso I decided to share some things in this post.<\/p>\n\n<p>My stack is the following:<\/p>\n<ul>\n  <li>main language: ruby<\/li>\n  <li>text editor: Sublime Text 3<\/li>\n  <li>OS: Ubuntu 16.04<\/li>\n  <li>terminal: gnome terminal with Tmux (I recently migrated from terminator and I\nwill explain why in a following post)<\/li>\n<\/ul>\n\n<p>This is the 1st of N posts, and I will share a few things I use in the text\neditor (which in my case is Sublime, but the actions should be applied in other\neditors).<\/p>\n\n<h2 id=\"agility-in-text-editor\">Agility in text editor<\/h2>\n\n<h3 id=\"1-goto-anything-or-fuzzy-finder\">1. Goto anything (or fuzzy finder)<\/h3>\n\n<p>I\u2019ve seen some developers struggling to find a file in the source tree,\nusing the mouse, when they can use a much faster way to open the files.<\/p>\n\n<p>Use the \u201cgoto anything\u201d alias, type a piece of the file name and opens the file\n<strong>without touching the mouse<\/strong>!<\/p>\n\n<p>In sublime text 3, just use the alias <code class=\"language-plaintext highlighter-rouge\">ctrl + p<\/code>:<\/p>\n\n<p><img src=\"\/agility_assets\/goto_anything.gif\" alt=\"go to anything\" \/><\/p>\n\n<h3 id=\"2-switch-projects\">2. Switch projects<\/h3>\n\n<p>In my opinion there as 3 ways to organize all the projects you work:<\/p>\n\n<ol>\n  <li>adding all folders (of each project) to the same workspace using only 1\ninstance of the text editor<\/li>\n  <li>creating a project (or workspace) for each project and keep open only 1\ninstance of the text editor<\/li>\n  <li>creating a project (or workspace) for each project and keep open\n1 instance of the text editor for each project you are working on<\/li>\n<\/ol>\n\n<p>In my opinion, the option 2 is the best to reduce the amount of files you can\nsearch using the <code class=\"language-plaintext highlighter-rouge\">goto anything<\/code> and to speed up when you need to open other\nproject.<\/p>\n\n<p>In sublime, to switch to another project, just press <code class=\"language-plaintext highlighter-rouge\">ctrl + alt + p<\/code>:<\/p>\n\n<p><img src=\"\/agility_assets\/switch_project.gif\" alt=\"switch project\" \/><\/p>\n\n<p>I also use the plugin <a href=\"https:\/\/github.com\/randy3k\/ProjectManager\">ProjectManager<\/a> to\nhelp creating and organizing the projects. But it is possible to do it by\nyourself.<\/p>\n\n<h3 id=\"3-go-to-definition\">3. Go to definition<\/h3>\n\n<p>If I was using an IDE or\/and a static typed language, this should be the default\nbehavior, but using ruby (without Sorbet or something similar) and sublime text\nit is not.<\/p>\n\n<p>I\u2019ve seen some devs using grep, awk or \u201cfind in project\u201d to search for a\nmethod definition, but with sublime there are other 2 ways faster to do it:<\/p>\n\n<h4 id=\"31-native-go-to-definition-f12\">3.1. Native go to definition (F12)<\/h4>\n\n<p>This command is native and tries to find the method definition, but it is not\nalways precise. To jump back were the cursor was, it is possible to use the\ncommand <code class=\"language-plaintext highlighter-rouge\">alt + -<\/code><\/p>\n\n<p><img src=\"\/agility_assets\/native_go_to_definition.gif\" alt=\"native go to definition\" \/><\/p>\n\n<h4 id=\"32-using-ctags\">3.2. Using CTags<\/h4>\n\n<p><a href=\"https:\/\/github.com\/universal-ctags\/ctags\">CTags<\/a> is something magical that can\nindex the source code and help to find the definitions for a lot of languages.\nIt is not 100% precise, but I think it is better than the native one.<\/p>\n\n<p>To use it, it is necessary to install the plugin <a href=\"https:\/\/github.com\/SublimeText\/CTags\">CTags<\/a>.<\/p>\n\n<p>First, it is necessary to build the index of the current project, just use <code class=\"language-plaintext highlighter-rouge\">ctrl + t + r<\/code>.<\/p>\n\n<p>Then, you can try to go to a definition using <code class=\"language-plaintext highlighter-rouge\">ctrl + t + t<\/code>.<\/p>\n\n<p>To jump back, you can use <code class=\"language-plaintext highlighter-rouge\">ctr + t + b<\/code>.<\/p>\n\n<p>Sometimes CTags will not be sure the method you want to go and will show the\noptions he found.<\/p>\n\n<p><img src=\"\/agility_assets\/goto_ctags.gif\" alt=\"goto ctags\" \/><\/p>\n\n<p>If both methods fail to find the definition, then I use the \u201cfind in project\u201d\nwith <code class=\"language-plaintext highlighter-rouge\">ctrl + shift + f<\/code>.<\/p>\n\n<h2 id=\"wrapping-up\">Wrapping up<\/h2>\n\n<p>This is only the 1st part of others that I want to write about this subject.\nIf this help someone anyway, please add a comment.<\/p>\n\n<p>See you!<\/p>","author":{"name":{}},"summary":"I\u2019ve been programming for a while and every day I try to learn something new to be more effective and fast."},{"title":"10 tips to help using the VCR gem in your Ruby test suite","link":{"@attributes":{"href":"https:\/\/fabioperrella.github.io\/10_tips_to_help_using_the_VCR_gem_in_your_ruby_test_suite.html","rel":"alternate","type":"text\/html","title":"10 tips to help using the VCR gem in your Ruby test suite"}},"published":"2019-07-08T00:00:00+00:00","updated":"2019-07-08T00:00:00+00:00","id":"https:\/\/fabioperrella.github.io\/10_tips_to_help_using_the_VCR_gem_in_your_ruby_test_suite","content":"<p>The original post, in Portuguese, was published <a href=\"https:\/\/imasters.com.br\/ruby\/10-dicas-para-facilitar-o-uso-da-gem-vcr-nos-testes-da-sua-app-ruby\">here<\/a>. (I procrastinated to translate it to English almost 1 year!)<\/p>\n\n<p>The gem <a href=\"https:\/\/github.com\/vcr\/vcr\">VCR<\/a> is a good choice to do integrated tests in Ruby apps. It can be used in other languages too, but it will be not covered in this post.<\/p>\n\n<p>It let us automate the process of stubbing the web requests through the gem <a href=\"https:\/\/github.com\/bblimke\/webmock\">Webmock<\/a> (or other similar). In order to do it, it records <em>cassette files<\/em> with all the HTTP requests and responses to external APIs. By doing this, it allows us to execute the test suite fastly and not depending on their state and disponibility of these APIs.<\/p>\n\n<p>However, when a test suite starts to get bigger, it is necessary to care about some things to help on the maintenance and avoid turning into a nightmare.<\/p>\n\n<p>I will list some tips and tricks to accomplish it.<\/p>\n\n<p>The examples are using the gem <a href=\"https:\/\/github.com\/rspec\/rspec\">rspec<\/a> in a <a href=\"https:\/\/rubyonrails.org\/\">rails<\/a> project, but VCR can be used with other frameworks, like <a href=\"https:\/\/github.com\/sinatra\/sinatra\">sinatra<\/a> with <a href=\"https:\/\/github.com\/seattlerb\/minitest\">minitest<\/a>.<\/p>\n\n<h2 id=\"1-setup-vcr-to-generate-the-cassette-names-automatically\">1. Setup VCR to generate the cassette names automatically<\/h2>\n\n<p>To avoid using the block <code class=\"language-plaintext highlighter-rouge\">VCR.use_cassette<\/code> in all scenarios and besides that, having to name all the cassettes, when using with rspec, it is possible to mark each scenario that will use VCR with the symbol <code class=\"language-plaintext highlighter-rouge\">:vcr<\/code>, as follows:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">describe<\/span> <span class=\"no\">SomeApi<\/span> <span class=\"k\">do<\/span>\n  <span class=\"n\">it<\/span> <span class=\"s1\">'creates the product'<\/span><span class=\"p\">,<\/span> <span class=\"ss\">:vcr<\/span> <span class=\"k\">do<\/span>\n    <span class=\"c1\"># test...<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>To do this, it is necessary to use the configuration below in VCR:<\/p>\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"no\">VCR<\/span><span class=\"p\">.<\/span><span class=\"nf\">configure<\/span> <span class=\"k\">do<\/span> <span class=\"o\">|<\/span><span class=\"n\">c<\/span><span class=\"o\">|<\/span>\n  <span class=\"n\">c<\/span><span class=\"p\">.<\/span><span class=\"nf\">configure_rspec_metadata!<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>By doing this, it will be created a cassette file according to the current context, for example:\n<code class=\"language-plaintext highlighter-rouge\">spec\/fixtures\/vcr_cassettes\/SomeApi\/creates_the_product.yml<\/code><\/p>\n\n<p>More details in <a href=\"https:\/\/relishapp.com\/vcr\/vcr\/v\/2-4-0\/docs\/test-frameworks\/usage-with-rspec-metadata\">rspec docs<\/a>.<\/p>\n\n<h2 id=\"2-setup-vcr-to-record-the-cassettes-just-once\">2. Setup VCR to record the cassettes just once<\/h2>\n\n<p>The VCR gem has some available record modes. It is recommended to use the mode <code class=\"language-plaintext highlighter-rouge\">:once<\/code> which will record the cassette only if it does not exist.<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"no\">VCR<\/span><span class=\"p\">.<\/span><span class=\"nf\">configure<\/span> <span class=\"k\">do<\/span> <span class=\"o\">|<\/span><span class=\"n\">c<\/span><span class=\"o\">|<\/span>\n  <span class=\"n\">vcr_mode<\/span> <span class=\"o\">=<\/span> <span class=\"ss\">:once<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>After the file is recorded, if this scenario tries to call the APIs with different parameters or try to call other APIs, the test will break, as follows:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>VCR::Errors::UnhandledHTTPRequestError:\n\n================================================================================\nAn HTTP request has been made that VCR does not know how to handle:\n GET http:\/\/someapi.com?lala=popo\n Body:\n\nVCR is currently using the following cassette:\n - \/home\/fabioperrella\/workspace\/some-app\/spec\/fixtures\/vcr_cassettes\/some_api\/some_vcr_.yml\n   - :record =&gt; :once\n   - :match_requests_on =&gt; [:method, :uri, :body]\n<\/code><\/pre><\/div><\/div>\n\n<p>The advantage of using this way, is to be sure that the cassettes are enough to run all the tests offline and allow us to do the next tip, which is disallowing external requests!<\/p>\n\n<h2 id=\"21-ignore-the-headers-to-match-the-cassete\">2.1 Ignore the headers to match the cassete<\/h2>\n\n<p>I strongly recommend to use this configuration<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"no\">VCR<\/span><span class=\"p\">.<\/span><span class=\"nf\">configure<\/span> <span class=\"k\">do<\/span> <span class=\"o\">|<\/span><span class=\"n\">c<\/span><span class=\"o\">|<\/span>\n  <span class=\"n\">c<\/span><span class=\"p\">.<\/span><span class=\"nf\">match_requests_on<\/span><span class=\"p\">:<\/span> <span class=\"sx\">%i[method uri body]<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This will avoid errors when some header changes. Once a time, I saw a header changing when running ruby in Mac OS or Linux!<\/p>\n\n<p>The default configuration is only <code class=\"language-plaintext highlighter-rouge\">[method, uri]<\/code>, but in my opinion it is important to compare the body too!<\/p>\n\n<p>More details in <a href=\"https:\/\/relishapp.com\/vcr\/vcr\/v\/1-6-0\/docs\/cassettes\/request-matching\">rspec docs<\/a><\/p>\n\n<h2 id=\"3-disallow-external-requests\">3. Disallow external requests<\/h2>\n\n<p>Be able to run the test suite offline, not caring about the state and disponibility of external APIs is our goal!<\/p>\n\n<p>Disallowing external requests is already the default behavior of VCR, and can be changed using the config <code class=\"language-plaintext highlighter-rouge\">allow_http_connections_when_no_cassette<\/code>, but don\u2019t do it!<\/p>\n\n<p>Remember it is required to do the previous tip to achieve this successfully.<\/p>\n\n<h2 id=\"4-have-a-way-to-record-the-cassettes-again-easily\">4. Have a way to record the cassettes again easily<\/h2>\n\n<p>Sometimes, it is necessary to change a test scenario (or the source code) which already has a cassette recorded and can be necessary to record it again.<\/p>\n\n<p>The trivial way would be deleting the current file and recording it again.<\/p>\n\n<p>But it is possible to use the following configuration, which will provide an environment variable to indicate that the cassette should be recorded again.<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"no\">VCR<\/span><span class=\"p\">.<\/span><span class=\"nf\">configure<\/span> <span class=\"k\">do<\/span> <span class=\"o\">|<\/span><span class=\"n\">c<\/span><span class=\"o\">|<\/span>\n  <span class=\"n\">vcr_mode<\/span> <span class=\"o\">=<\/span> <span class=\"no\">ENV<\/span><span class=\"p\">[<\/span><span class=\"s1\">'VCR_MODE'<\/span><span class=\"p\">]<\/span> <span class=\"o\">=~<\/span> <span class=\"sr\">\/rec\/i<\/span> <span class=\"p\">?<\/span> <span class=\"ss\">:all<\/span> <span class=\"p\">:<\/span> <span class=\"ss\">:once<\/span>\n\n  <span class=\"n\">c<\/span><span class=\"p\">.<\/span><span class=\"nf\">default_cassette_options<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n    <span class=\"ss\">record: <\/span><span class=\"n\">vcr_mode<\/span><span class=\"p\">,<\/span>\n    <span class=\"ss\">match_requests_on: <\/span><span class=\"sx\">%i[method uri body]<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>By doing this, it is possible to run a scenario as following:<\/p>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>VCR_MODE=rec bundle exec rspec spec\/some_class_spec.rb:30\n<\/code><\/pre><\/div><\/div>\n\n<p>But <strong>be careful<\/strong>, if you run all the suite with this ENV, it will record all the cassettes!<\/p>\n\n<h2 id=\"5-use-the-vcrcurrent_cassettefile-to-know-where-the-cassette-file-is-stored\">5. Use the VCR.current_cassette.file to know where the cassette file is stored<\/h2>\n\n<p>Using the symbol <code class=\"language-plaintext highlighter-rouge\">:vcr<\/code> to enable the VCR in a scen\u00e1rio help us in the task of naming and organizing the cassette files, but it turns difficult when we want to know where the cassette file is.<\/p>\n\n<p>To help with it, it is possible to use the method bellow:<\/p>\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">it<\/span> <span class=\"s1\">'does somethid'<\/span><span class=\"p\">,<\/span> <span class=\"ss\">:vcr<\/span> <span class=\"k\">do<\/span>\n  <span class=\"nb\">puts<\/span> <span class=\"no\">VCR<\/span><span class=\"p\">.<\/span><span class=\"nf\">current_cassette<\/span><span class=\"p\">.<\/span><span class=\"nf\">file<\/span>\n  <span class=\"c1\"># test ...<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Extra tip: have a snippet in you text editor to generate this line. For example I have <a href=\"https:\/\/github.com\/fabioperrella\/dotfiles\/blob\/master\/sublime\/current_vcr.sublime-snippet\">one for it<\/a>.<\/p>\n\n<h2 id=\"6-be-careful-with-the-sequences-in-factories\">6. Be careful with the <em>sequences<\/em> in factories<\/h2>\n\n<p>When using the gem <a href=\"https:\/\/github.com\/thoughtbot\/factory_bot\">factory_bot<\/a>, it is possible to create <a href=\"https:\/\/github.com\/thoughtbot\/factory_bot\/blob\/master\/GETTING_STARTED.md#sequences\">sequences<\/a> to generate sequential values for the attributes.<\/p>\n\n<p>Depending on the order the tests run, these values will be different and because of it, it can break some scenarios with VCR because the payload or the query string of a request will be different comparing with the recorded one.<\/p>\n\n<p>It is a good practice to run the suite in a random order, to detect when a scenario depends on another one (it should be indepentent), because of it, in scenarios using VCR and a factory, it is recommended to set fixed values in attributes which would be generated by a sequence as following:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">#spec\/factories\/cars.rb<\/span>\n<span class=\"no\">FactoryBot<\/span><span class=\"p\">.<\/span><span class=\"nf\">define<\/span> <span class=\"k\">do<\/span>\n  <span class=\"n\">factory<\/span> <span class=\"ss\">:product<\/span> <span class=\"k\">do<\/span>\n    <span class=\"n\">description<\/span> <span class=\"s2\">\"some product\"<\/span>\n    <span class=\"n\">sequence<\/span><span class=\"p\">(<\/span><span class=\"ss\">:sku<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span> <span class=\"o\">|<\/span><span class=\"n\">n<\/span><span class=\"o\">|<\/span> <span class=\"s2\">\"SKU-<\/span><span class=\"si\">#{<\/span><span class=\"n\">n<\/span><span class=\"si\">}<\/span><span class=\"s2\">\"<\/span> <span class=\"p\">}<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n\n<span class=\"c1\">#spec\/car_api_spec.rb<\/span>\n<span class=\"n\">describe<\/span> <span class=\"no\">CartCreation<\/span> <span class=\"k\">do<\/span>\n  <span class=\"n\">it<\/span> <span class=\"s2\">\"add the product to the cart\"<\/span><span class=\"p\">,<\/span> <span class=\"ss\">:vcr<\/span> <span class=\"k\">do<\/span>\n    <span class=\"n\">product<\/span> <span class=\"o\">=<\/span> <span class=\"n\">build<\/span><span class=\"p\">(<\/span><span class=\"ss\">:product<\/span><span class=\"p\">,<\/span> <span class=\"ss\">sku: <\/span><span class=\"s1\">'SKU-33'<\/span><span class=\"p\">)<\/span> <span class=\"c1\"># if not setting sku, the VCR can break!<\/span>\n    <span class=\"n\">response<\/span> <span class=\"o\">=<\/span> <span class=\"no\">CartCreation<\/span><span class=\"p\">.<\/span><span class=\"nf\">create<\/span><span class=\"p\">(<\/span><span class=\"n\">product<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"n\">expect<\/span><span class=\"p\">(<\/span><span class=\"n\">response<\/span><span class=\"p\">).<\/span><span class=\"nf\">to<\/span> <span class=\"n\">eq<\/span><span class=\"p\">(<\/span><span class=\"ss\">:ok<\/span><span class=\"p\">)<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<h2 id=\"7-always-let-the-scenario-ready-to-be-re-recorded\">7. Always let the scenario ready to be re-recorded<\/h2>\n\n<p>Sometimes, it is necessary to re-record some cassettes. If the setup of the scenario is not prepared, it can turn into a difficult task.<\/p>\n\n<p>For instance, testing an API which will delete a resource in the server:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">it<\/span> <span class=\"s1\">'deletes the resource'<\/span><span class=\"p\">,<\/span> <span class=\"ss\">:vcr<\/span>\n  <span class=\"n\">resource_id<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">40<\/span> <span class=\"c1\"># ID wich exists in some place<\/span>\n\n  <span class=\"n\">response<\/span> <span class=\"o\">=<\/span> <span class=\"no\">SomeApi<\/span><span class=\"p\">.<\/span><span class=\"nf\">delete<\/span><span class=\"p\">(<\/span><span class=\"n\">resource_id<\/span><span class=\"p\">)<\/span>\n\n  <span class=\"n\">expect<\/span><span class=\"p\">(<\/span><span class=\"n\">response<\/span><span class=\"p\">).<\/span><span class=\"nf\">to<\/span> <span class=\"n\">eq<\/span><span class=\"p\">(<\/span><span class=\"ss\">:o<\/span><span class=\"p\">)<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>In the first time runnnin the scenario, the cassette will be recorded and it will work.<\/p>\n\n<p>But when something changes, when trying to record the cassette again, the resource with id 40 will not exist anymore and the execution will fail!<\/p>\n\n<p>One option to let this scenario <a href=\"https:\/\/en.wikipedia.org\/wiki\/Idempotence\">idempotent<\/a> is creating the resource in the setup as following:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">it<\/span> <span class=\"s1\">'deletes the resource'<\/span><span class=\"p\">,<\/span> <span class=\"ss\">:vcr<\/span>\n  <span class=\"c1\"># setup<\/span>\n  <span class=\"n\">resource<\/span> <span class=\"o\">=<\/span> <span class=\"no\">SomeApi<\/span><span class=\"p\">.<\/span><span class=\"nf\">create<\/span>\n\n  <span class=\"c1\"># exercise<\/span>\n  <span class=\"n\">response<\/span> <span class=\"o\">=<\/span> <span class=\"no\">SomeApi<\/span><span class=\"p\">.<\/span><span class=\"nf\">delete<\/span><span class=\"p\">(<\/span><span class=\"n\">resource<\/span><span class=\"p\">.<\/span><span class=\"nf\">id<\/span><span class=\"p\">)<\/span>\n\n  <span class=\"c1\"># verify<\/span>\n  <span class=\"n\">expect<\/span><span class=\"p\">(<\/span><span class=\"n\">response<\/span><span class=\"p\">).<\/span><span class=\"nf\">to<\/span> <span class=\"n\">eq<\/span><span class=\"p\">(<\/span><span class=\"ss\">:ok<\/span><span class=\"p\">)<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<h2 id=\"8-be-careful-with-caches-and-the-execution-order\">8. Be careful with caches and the execution order<\/h2>\n\n<p>A good practice is to run the tests in random order to force the scenarios do not depend on the others.<\/p>\n\n<p>With Rspec, it is possible to do it with the following configuration:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"no\">RSpec<\/span><span class=\"p\">.<\/span><span class=\"nf\">configure<\/span> <span class=\"k\">do<\/span> <span class=\"o\">|<\/span><span class=\"n\">config<\/span><span class=\"o\">|<\/span>\n  <span class=\"n\">config<\/span><span class=\"p\">.<\/span><span class=\"nf\">order<\/span> <span class=\"o\">=<\/span> <span class=\"s1\">'random'<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Using VCR as suggested in this article, with the configurations <code class=\"language-plaintext highlighter-rouge\">record: :once<\/code> and with <code class=\"language-plaintext highlighter-rouge\">allow_http_connections_when_no_cassette=false<\/code>, any request made with a different URL, method or body than it was recorded in the cassette, will break the test.<\/p>\n\n<p>A common scenario is caching an authentication token API, for example.<\/p>\n\n<p>With the cache turned on, the first cassette recorded will have the request to get the token, but the others no (because it will use from the cache), like this:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code># first cassette\nPOST http:\/\/some-api.com\/authentication\nGET http:\/\/some-api.com\/users?token=1234\n\n# second cassette\nGET http:\/\/some-api.com\/products?token=1234\n<\/code><\/pre><\/div><\/div>\n\n<p>When running the tests in random order, if the test with the second cassette runs first, it will break because it will try to do a request to create the token which is not recorded.<\/p>\n\n<p>Turning off the token cache, the cassettes would be recorded like this:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code># first cassette\nPOST http:\/\/some-api.com\/authentication\nGET http:\/\/some-api.com\/users?token=1234\n\n# second cassette\nPOST http:\/\/some-api.com\/authentication\nGET http:\/\/some-api.com\/products?token=3456\n<\/code><\/pre><\/div><\/div>\n\n<p>With this strategy, it is possible to run the scenarios in any order!<\/p>\n\n<h2 id=\"9-normalize-replace-the-api-urls-on-cassettes\">9. Normalize (replace) the API URLs on cassettes<\/h2>\n\n<p>To avoid the tests with VCR brake when a URL of an API changes, it is possible to use the following configuration to normalize the URLs:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"no\">VCR<\/span><span class=\"p\">.<\/span><span class=\"nf\">configure<\/span> <span class=\"k\">do<\/span> <span class=\"o\">|<\/span><span class=\"n\">c<\/span><span class=\"o\">|<\/span>\n  <span class=\"n\">c<\/span><span class=\"p\">.<\/span><span class=\"nf\">filter_sensitive_data<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"&lt;SOME_API&gt;\"<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span> <span class=\"s1\">'some-api.com'<\/span> <span class=\"p\">}<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>If using environment variables to configure these URLs, we can do it in a smarter way:<\/p>\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"no\">VCR<\/span><span class=\"p\">.<\/span><span class=\"nf\">configure<\/span> <span class=\"k\">do<\/span> <span class=\"o\">|<\/span><span class=\"n\">c<\/span><span class=\"o\">|<\/span>\n  <span class=\"sx\">%w[\n    SOME_API_URL\n    OTHER_API_URL\n  ]<\/span><span class=\"p\">.<\/span><span class=\"nf\">each<\/span> <span class=\"k\">do<\/span> <span class=\"o\">|<\/span><span class=\"n\">key<\/span><span class=\"o\">|<\/span>\n    <span class=\"n\">c<\/span><span class=\"p\">.<\/span><span class=\"nf\">filter_sensitive_data<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"&lt;<\/span><span class=\"si\">#{<\/span><span class=\"n\">key<\/span><span class=\"si\">}<\/span><span class=\"s2\">&gt;\"<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span> <span class=\"no\">ENV<\/span><span class=\"p\">[<\/span><span class=\"n\">key<\/span><span class=\"p\">]<\/span> <span class=\"p\">}<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>By doing this, it is easy to use a different URL (or port) in the development environment, for example, if running with Docker.<\/p>\n\n<h2 id=\"10-ignore-the-cassette-diffs-in-merge-requests\">10. Ignore the cassette diffs in merge requests<\/h2>\n\n<p>Whenever we create a merge request, we need to take care to not let the diff so huge to help the review process by the team.<\/p>\n\n<p>In some situations, a little change in the source-code can bring a lot of changes in the cassettes, because they needed to be recorded again.<\/p>\n\n<p>To avoid this noise in the diffs, it is possible to use the file <a href=\"https:\/\/git-scm.com\/docs\/gitattributes\">.gitattributes<\/a> to hide them. The majority of the git hosts (such as github, gitlab) can recognize it:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>* text=auto\n\nspec\/fixtures\/vcr_cassettes\/**\/* -diff\n<\/code><\/pre><\/div><\/div>\n\n<p>In this case, the diff of the files will be hidden in MRs.<\/p>\n\n<p><strong>Attention<\/strong>: the diff will be hidden also in all git clients, for example, <code class=\"language-plaintext highlighter-rouge\">tig<\/code> or <code class=\"language-plaintext highlighter-rouge\">gitx<\/code>, but it is possible to remove (or comment) this file temporarily to see the diff in the local environment, when necessary.<\/p>\n\n<h2 id=\"wrapping-up\">Wrapping Up<\/h2>\n\n<p>I hope these tips and tricks can help the devs using tests with VCR, which is a great tool in my opinion!<\/p>\n\n<p>If you have any comments or suggestions, please leave a comment below.<\/p>\n\n<p>See you next!<\/p>","author":{"name":{}},"summary":"The original post, in Portuguese, was published here. (I procrastinated to translate it to English almost 1 year!)"}]}