Null Object Pattern Example in JS

I’ve been taking JS courses on Execute Program, which as been excellent.

I’m currently learning about Array’s in-depth and wondering if this is a readable way to achieve the null object pattern. No conditionals!

const sales_tax = [
  { name: 'WA', tax: 0.10 },
  { name: 'OR', tax: 0.9 },
  ];

sales_tax[-1]  = { state: 'nan', tax: 0 }
// -1 index is not in `keys` so forEach and other enumerators will not access it

sales_tax.forEach( state => console.log(`State: ${state.name}`));
// State: WA
// State: OR
// See, no `nan`!

console.log( sales_tax.map( state => state.name))
// [ 'WA', 'OR' ]
// no `nan` here either

const customers = [
  { name: 'Travis', state: 'WA' },
  { name: 'Tiny Travis', state: 'XX' },
  ];

const payable_tax = (state) => {
  // if I did a look up with `find` it would return `undefined`. 
  // By retrieving the index for look up, I instead get -1  

  const index = sales_tax.findIndex( e => e.name === state );
  return sales_tax[index].tax; // No nil check, just call tax!
}

customers.forEach( customer => console.log(`${customer.name} will pay ${payable_tax(customer.state) * 5.00} in taxes`));
// Travis will pay 0.5 in taxes
// Tiny Travis will pay 0 in taxes

This JS code is confident. It never has to check state , handle nil or undefined.

Effective Testing with RSpec 3, Structuring Code Examples

Notes from Effective Testing with RSpec 3, chapter 7.

In Chapter 7 we go in-depth on the different ways of organizing code and making specs readable.

“…readable specs are crucial for communicating your ideas and for long-term maintainability. They show the intent behind the code.”

Excerpt From: Myron Marston, Ian Dees. “Effective Testing with RSpec 3”

Ways of organizing specs

  # Use a class.
  RSpec.describe Class do

  # Use an object.
  RSpec.describe object do

  # Provide more context in a string.
  RSpec.describe object, 'an interface' do

  # Add a tag for configuring in Hooks and filtering.
  RSpec.describe Class, 'an adapter library', issue: ABC-123 do
  
  RSpec.describe 'a class' do
    # `context` is an alias for `describe`.
    context 'in this environment' do
      it 'does one thing'
      it 'does another'
    end
    
    describe 'data structure' do
      # `example` is an alias for `it`.
      example 'in ordered form'
      example 'in unordered form'
    end
    
    describe 'api config' do
      # `specify` is an alias for `it`.      
      specify 'initializer fails when api is not defined'
      specify 'APIClient.gem specifies dependencies'
    end
  end

If the above aliases are not enough to make your specs readable you can add your own aliases. Create an alias on groups or examples and add metadata for filtering.

Ways of Sharing Logic

This section is mostly review of what we already know.

  • let method, which uses memoization.
  • hooks, run code for setup & teardown. Hooks can happen on examples, groups, the entire suite, or configured for tags.
  • Helper methods, regular ruby method, to reduce complexity.
  • Modules, regular ruby module, to reduce complexity.
  • Sharing Context and Examples to share code in the test context.

We have to be careful when sharing code in our test suites. Remember that it’s not production code. If I sacrifice readability and maintainability for optimization it is a step backward not forward.

Specs should be modular. When it fails we should have everything we need to understand the failure in front of us to resolve the issue.

Refactoring Exercise

I was a little ambitious in my refactoring. After comparing my first refactoring to the books example I realized I had missed the point. The goal of our test suit is to be easy to read in the code and execution.

This is the rspec output of my first attempt. I extracted ‘behaves like Parser’ to share between the specs.

Addressable::URI
  parses the scheme
  parses the path
  behaves like Parser
    identifies schemes
      host
      port

URI
  behaves like Parser
    identifies schemes
      host
      port
  port
    defaults http to 80
    defaults https to 443

It doesn’t read very well as each line is missing context. We cannot randomly pick up a line and understand what we are testing. We have to scan up to know what is happening. The next refactoring has better readability.

Addressable::URI
  parses the scheme
  parses the path
  behaves like a URI Parser
    identifies the host
    identifies the port

URI
  behaves like a URI Parser
    identifies the host
    identifies the port
  port
    defaults http to 80
    defaults https to 443

I’ve uploaded my refactoring to GitHub.

Effective Testing with RSpec 3, Getting Real: Integration Specs

Notes from Effective Testing with RSpec 3, chapter 6.

Integration Specs

As we learned from previous chapters, Integration Specs test code that depends on an api or code that cannot change. I’ve been talking about how Effective Testing categorizes specs differently than I do. Here we see the model layer being considered an Integration Spec because it has a SQL dependency. We test the Ledger class’s capabilities to interact with the database.

I’m starting to see the lines between test categories. In a Rails context, our models are unit tested because we’re testing the class itself; are the validations working, can it handle currency in different formats, does the behavior change depending on state, etc. We do not test the capabilities to store data because it’s a responsiblity of the Rails Framework. Testing behavior that a language or framework provides is usually unneccessary and will bloat the test suite.

Effective Testing is saying Ledgers dependency on SQL is covered by an Integration spec. While Ledger’s behavior is tested in an Acceptence spec.

Martin Fowler has recently written about Integration Specs. It provides an in depth explanation of different ways people think of Integration Specs.

Bisect

Running specs in random order unveils problems that we haven’t thought of. RSpec provides a way to repeat the random order by providing a seed which you can pass back rspec –seed 32043. RSpec provides us another handy trick, bisect. We can set the seed flag, pass –bisect and RSpec will isolate the required tests and their order to reproduce the issue.

A cost of bloated test suites is the time and attention it takes to read through specs and understand an issue when they pop up. Using bisect we can cut out the fluff and just get to the essentials of understanding the problem. This will help reduce the cognitive load of shifting through specs.

Metatags Revisited

We’ve been using metatags to filter examples that are run. Chapter 6 demonstrates another trick. We can use metatags to run configuration steps on specific examples.

In our spec_helper we can define the following config:

  config.when_first_matching_example_defined(:db) do
    require_relative 'support/db'
  end
  

Then any spec can include :db to run require and run the code in ‘support/db’.

     RSpec.describe 'Expense Tracker API', :db do
  

This is very handy. In the past I have needed to setup a mock web request for 30% of the test suite. I ended up putting it in a before hook in the config because it was faster than updating 30% of the examples. Using metatags is a cleaner solution: you only run the code that you need and the example declares it’s dependencies instead of hiding them.