Skip to content

Instantly share code, notes, and snippets.

@PandaWhisperer
Last active March 16, 2017 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PandaWhisperer/ff1fd658339b077faa791c6f03d26b4e to your computer and use it in GitHub Desktop.
Save PandaWhisperer/ff1fd658339b077faa791c6f03d26b4e to your computer and use it in GitHub Desktop.

JS: Celsius to Fahrenheit (with tests)

Solving the Firehose Javascript challenge Celsius to Fahrenheit with tests.

Introduction

Before we begin, there is a piece of information missing from the challenge. When you run npm install --save readline-sync, you'll likely see an error like this:

npm WARN enoent ENOENT: no such file or directory, open '/Users/chris/Work/theFirehoseProject/JS/package.json'
npm WARN JS No description
npm WARN JS No repository field.
npm WARN JS No README data
npm WARN JS No license field.

This is telling us that the package.json file, which is standard for any Node.js project, is missing from our project directory. Luckily, there is an easy way to fix that. The npm command itself contains a tool to generate that file for us. Simply run npm init, and it will ask you a couple of questions about your project, and then generate the file for you.

NOTE: as an aside, the purpose of the package.json file in Node.js is similar to that of the Gemfile in Ruby: it contains a list of all the packages (gems in Ruby) that need to be present for the program to run. Unlike Ruby's Gemfile, however, the package.json file also contains metadata, i.e. information about the project (such as name, description, version number, name of the author, and homepage). All of these, except name and version number, are optional.

Installing Mocha.js

Okay, at this point I'm assuming you've already solved the challenge (not hard!), and your cel2fah.js looks something like this:

var readlineSync = require('readline-sync');

var degrees = readlineSync.question('Enter degrees in Celsius: ');
var degreesNum = Number(degrees);
var degreesFahrenheit = degreesNum * 1.8 + 32;
console.log('It is ' + degreesFahrenheit + ' degrees Fahrenheit!');

Next, we'll install Mocha. Thanks to npm, this is very simple (but different from Ruby): simply run npm install --save-dev mocha.

This will install Mocha and update the package.json file for you, recording the Mocha dependency.

NOTE: you may be wondering at this point about the difference between NPM's --save and --save-dev flags. Similar to Gemfiles, where you can define different groups of gems (such as development and production), and thus mark them to be installed only in specific environments, NPM has the concept of "runtime dependencies" and "development dependencies". Basically, runtime dependencies are packages that must be present in order to run the code at all (such as on a server), while development dependencies are packages that only need to be installed in order to work on the project. Generally, any tool such as test frameworks (mocha), task runners (grunt, gulp), preprocessors (SCSS, Stylus), and so on will be installed as development dependencies.

Before we move on, however, since Mocha is a command line tool, we must also install it globally, so we can run it from the terminal. Simply run npm install --global mocha and you should be good to go. To test if this was successful, type mocha (followed by enter) at the terminal. You should see something like this:

Warning: Could not find any test files matching pattern: test
No test files found

Of course, we don't have any tests yet!

Writing our first test

Okay, let's start by creating a dummy test just so we can see mocha pick it up and run it. Create a new directory called test, and inside, create a file called cel2fah_spec.js (unlike RSpec, Mocha expects to find tests in the test directory by default. However, just like RSpec, it still expects them to end with _spec.js).

For now, we'll just put this in there:

console.log('Hello from Mocha!');

Run mocha again by typing mocha, followed by enter, and you should see this:

Hello from Mocha!


  0 passing (1ms)

So, no more error, our file was obviously found and executed. But there were no test cases in it, so we get 0 passing.

So now, let's actually write a test!

Change the file to look like this:

describe('A test', function() {
  it('tests something', function() {
    console.log('Hello from Mocha')
  });
});

After saving it and running mocha again, you should see this:

A test
Hello from Mocha!
  ✓ tests something


1 passing (7ms)

Great! Mocha found our test case, and since it didn't throw an exception, it considers it successful.

Now, this is not a very useful test yet, since it doesn't actually have an assertion (expectation). In other words, it cannot fail, it will always succeed. Let's fix that now:

var assert = require('assert');

describe('A test', function() {
  it('checks that 1 + 1 = 2', function() {
    assert.equal(1+1, 2)
  });
});

Now, let's run mocha one more time!

  A test
  ✓ checks that 1 + 1 = 2


1 passing (8ms)

Testing our code

So, now that we now how to write tests and run them, we should actually write some tests to check that our code is working properly. However, there is a problem with that: the current version expects to read a value from the keyboard and writes it directly to the screen! In order to test it, we need to be able to pass a value in and get a value out.

So, let's rewrite our code so we can actually test it!

If you take a close look at cel2fah.js, you notice quickly that all lines but one deal only with the process of reading the input from the terminal and writing it back out. The only line that actually does any computation is this one:

var degreesFahrenheit = degreesNum * 1.8 + 32;

This is the code we actually want to test!

So, what do we do? We just make it into a function!

function celsiusToFahrenheit(degrees) {
  return degrees * 1.8 + 32;
}

So then we could test it like this:

describe('celsiusToFahrenheit', function() {
  it('converts 0 Celcius to 32 Fahrenheit', function() {
    assert.equal(celsiusToFahrenheit(0), 32)
  });

  it('converts 30 Celcius to 86 Fahrenheit', function() {
    assert.equal(celsiusToFahrenheit(30), 86)
  });
});

Okay, but where do we put this function? And how can we access it from our test? In Ruby, we can use require_relative to import all the contents of a file. But in Node.js, it doesn't work like that.

Instead of explaining it in theoretical detail, I'll just show you how it's done and let the code speak for itself.

First, just as in Ruby, we'll want all our tested code in a directory called lib. So let's create lib/cel2fah.js and put our function there:

module.exports = function celsiusToFahrenheit(degrees) {
  return degrees * 1.8 + 32;
}

Note the module.exports assignment at the beginning. This is how we tell Node.js what from this file we want to be available to other files who require this file.

If you don't assign anything to this, requireing the file will load and execute all the code, but only return an empty object (so none of the code will be callable for another file).

Now, let's import our new code into our test file so we can use it:

var celsiusToFahrenheit = require('../lib/cel2fah.js');
var assert = require('assert');

describe('celsiusToFahrenheit', function() {
  it('converts 0 Celcius to 32 Fahrenheit', function() {
    assert.equal(32, celsiusToFahrenheit(0))
  });

  it('converts 30 Celcius to 86 Fahrenheit', function() {
    assert.equal(86, celsiusToFahrenheit(30))
  });
});

Note that there is no require_relative. In Node.js, you just use relative paths in require (which of course you can also do in Ruby, require_relative is just for convenience).

Also note that generally, anytime you use require in Node.js, you want to use the return value and assign it to a variable. That's because require returns whatever the required file assigns to module.exports. This could be an object, a function, or even just a value.

Let's run the tests to see if it works:

celsiusToFahrenheit
  ✓ converts 0 Celcius to 32 Fahrenheit
  ✓ converts 30 Celcius to 86 Fahrenheit


2 passing (7ms)

Now, we also need to change our main cel2fah.js to use our new file:

var celsiusToFahrenheit = require('./lib/cel2fah.js');
var readlineSync = require('readline-sync');

var degrees = readlineSync.question('Enter degrees in Celsius: ');
var degreesNum = Number(degrees);
var degreesFahrenheit = celsiusToFahrenheit(degreesNum);
console.log('It is ' + degreesFahrenheit + ' degrees Fahrenheit!');

Run node cel2fah.js again to check that it works. Done!

module.exports = function celsiusToFahrenheit(degrees) {
return degrees * 1.8 + 32;
}
var celsiusToFahrenheit = require('../lib/cel2fah.js');
var assert = require('assert');
describe('celsiusToFahrenheit', function() {
it('converts 0 Celcius to 32 Fahrenheit', function() {
assert.equal(celsiusToFahrenheit(0), 32)
});
it('converts 30 Celcius to 86 Fahrenheit', function() {
assert.equal(celsiusToFahrenheit(30), 86)
});
});
{
"name": "cel2fah",
"version": "1.0.0",
"description": "Converts degrees Celsius to Fahrenheit",
"main": "cel2fah.js",
"dependencies": {
"readline-sync": "^1.4.6"
},
"devDependencies": {
"mocha": "^3.2.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Christoph Wagner (theFirehoseProject) <firehose@christophwagner.me>",
"license": "ISC"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment