title: Rails: How to write model spec for this method?
body: <p>I've just started to take on my first <code>model spec</code> task at work. After writing a lot of feature specs, I find it hard to get into the different perspective of writing model specs (not taking the context into consideration). I'll take a method of the <code>Order</code> model as an example, to explain which difficulties I am experiencing:</p>
  
  <pre><code>def update_order_prices
    self.shipping_price_cents = SHIPPING_PRICE_CENTS unless shipping_price_cents
  
    return if order_lines.empty?
  
    self.total_price_cents = calculate_order_price
    self.total_line_items_price_cents = calculate_total_order_line_price
    self.total_tax_cents = calculate_tax_amount
  end
  </code></pre>
  
  <p><strong>EDIT TL;DR</strong></p>
  
  <p>I am totally happy with an answer that simply writes me a spec for this method. The rest of the post just shows what I tried so far but is not necessary to answer this question. </p>
  
  <p><strong>First approach:</strong></p>
  
  <p>At first I didn't know what to test for. I tried to find out when and where the method was called and to find a scenario where I would know what the attributes that are touched in this method should be equal to. Put short, I spent a lot of time trying to understand the context. Then a coworker said that I should test methods in model specs self-contained, independent from the context. I should just make sure I identify all cases. So for this method that would be:</p>
  
  <ul>
  <li>it sets shipping price cents to default (if not done already)</li>
  <li>it returns early if order_lines is empty</li>
  <li>it sets values if order_line is set</li>
  </ul>
  
  <p><strong>Current approach:</strong></p>
  
  <p>I tried writing the tests for these points but still questions arise:</p>
  
  <p><strong>Test 1</strong> </p>
  
  <pre><code>it 'sets shipping price cents to default (if not done already)' do
    order.shipping_price_cents = nil
    order.update_order_prices
  
    expect(order.shipping_price_cents).to eq(Order::SHIPPING_PRICE_CENTS)
  end
  </code></pre>
  
  <p>I am confident I got this one right, but feel free to prove me wrong. I set shipping_price_cents to nil to trigger the code that sets it, call the tested method on the cents to be equal to the default value as defined in the model.</p>
  
  <p><strong>Test 2</strong></p>
  
  <pre><code>it 'returns early if order_lines is empty' do
    expect(order.update_order_prices).to eq(nil)
  end
  </code></pre>
  
  <p>So here I want to test that the method returns early when there is no object in the <code>order_lines</code> association. I didn't have a clue how to do that so I went into the console, took an order, removed the order_lines associated with it, and called the method to see what would be returned.</p>
  
  <pre><code>2.3.1 :011 &gt; o.order_lines
  =&gt; #&lt;ActiveRecord::Associations::CollectionProxy []&gt;
  2.3.1 :012 &gt; o.update_order_prices
  =&gt; nil
  </code></pre>
  
  <p>Then did the same for an order with associated order_line:</p>
  
  <pre><code>2.3.1 :017 &gt; o.update_order_prices
  =&gt; 1661
  </code></pre>
  
  <p>So I tested for 'nil' to be returned. But it doesn't feel like I am testing the right thing.</p>
  
  <p><strong>Test 3</strong></p>
  
  <pre><code>it 'sets (the correct?) values if order_line is set' do
  
    order_line = create(:order_line, product: product)
    order = create(:order, order_lines: [order_line])
    order.update_order_prices
  
    expect(order.total_price_cents).to eq(order.calculate_order_price)
    expect(order.total_line_items_price_cents).to eq(order.calculate_order_line_price)
    expect(order.total_tax_cents).to eq(order.calculate_tax_amount)
  end
  </code></pre>
  
  <p>I simply test that the attributes equal what they are set to, without using actual values, as I shouldn't look outside. If I wanted to test for an absolute value, I would have to investigate outside of this function which then wouldn't test the method but also status of the Order object etc.?</p>
  
  <p><strong>Running the tests</strong></p>
  
  <pre><code>Failures:
  1) Order Methods: #update_order_prices sets (the correct?) values if order_line is set
   Failure/Error: expect(order.total_price_cents).to eq(order.calculate_order_price)
  
   NoMethodError:
     private method `calculate_order_price' called for #&lt;Order:0x007ff9ee643df0&gt;
     Did you mean?  update_order_prices
  </code></pre>
  
  <p>So, the first two tests passed, the third one didn't. At this point I feel a bit lost and would love hear how some experienced developers would write this seemingly simple test. </p>
  
  <p>Thanks</p>