JavaScript Test With Jasmine

Testing is a way that you describe your code on how it work and what it can do. Moreover, you can check if your code work like what you are expected or not.

Javascript Testing With Jasmine

Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.

Senario

We have form user signup and signin. We want to write jquery for form validation on username, email, and password. we want to check to make sure that username,email,password is not empty, have correct form, follow the length rule. If user input invalid information it will show error message after field lost focus.

Let write our test code on javascript class

For this article I am going to have a Jasmine demo on Rails server. For the article you need have some knowledge with RubyonRails. In this article I am using gem "jasminerice" you can fine more information here. By add gem into Rails APP and run setup command. It will generate some config file and folder for you to work with. Now you will have folder structure as follow like this:

- app
|__spec
|  |__ javascripts
|     |__ fixture
|     |__ spec.css
|     |__ spec.js.coffee

Now first let start writing our first javascript test by creating new file in folder javascripts with name users_spac.js.coffee. And in this file let write our test code:

describe "User class", ->
  user = null
  beforeEach ->
    user = new User(" RathanakJame ", " [email protected] ", " 1234567890 ")
  it "should trim out spacing for name,email and password", ->
    expect(user.name).toBe("RathanakJame")
    expect(user.email).toBe("[email protected]")
    expect(user.password).toBe("1234567890")
    expect(user.error_message).toBe("")

  describe "Name", ->
    it "should not empty", ->
      user.name = ""
      expect(user.validName()).not.toBe true
      expect(user.error_message).toEqual("User name is empty")

    it "should be able to use number", ->
      user.name = "name1234"
      expect(user.validName()).toBeTruthy()
      expect(user.error_message).toEqual("")

    it "should not has space or other symbol", ->
      user.name = "name space"
      expect(user.validName()).not.toBeTruthy()
      user.name = "[email protected]"
      expect(user.validName()).not.toBeTruthy()
      user.name = "name%^space"
      expect(user.validName()).not.toBeTruthy()
      expect(user.error_message).toEqual("Invalid User Name")

    it "should has min length 3", ->
      user.name = "as"
      expect(user.validName()).not.toBeTruthy()
      expect(user.error_message).toEqual("User name too sort")

    it "should has max length 16", ->
      user.name = "thelongestnameever"
      expect(user.validName()).not.toBeTruthy()
      expect(user.error_message).toEqual("User name too long")

  describe "Email", ->
    it "should not empty", ->
      user.email = ""
      expect(user.validEmail()).not.toBeTruthy()
      expect(user.error_message).toContain("Email is empty")

    it "should has correct email format", ->
      user.email = "[email protected]"
      expect(user.validEmail()).toBeTruthy()
      expect(user.error_message).toContain("")

    it "should accept Upcase letter", ->
      user.name = "[email protected]"
      expect(user.validEmail()).toBeTruthy()
      expect(user.error_message).toContain("")

    it "should not has other symbol", ->
      user.email = "e#ma&[email protected]"
      expect(user.validEmail()).toBeFalsy()
      expect(user.error_message).toContain("Invalid ")

    it "should has max length 50", ->
      user.email = "[email protected]"
      expect(user.validEmail()).not.toBeTruthy()
      expect(user.error_message).toContain("too long")

  describe "Password", ->
    it "should not empty", ->
      user.password = ""
      expect(user.validPassword()).not.toBeTruthy()
      expect(user.error_message).toBe("Password is empty")

    it "should has min length 6", ->
      user.password = "12345"
      expect(user.validPassword()).not.toBeTruthy()
      expect(user.error_message).toBe("Password too sort")

    it "should has max length 15", ->
      user.password = "password1234578901234567"
      expect(user.validPassword()).not.toBeTruthy()
      expect(user.error_message).toBe("Password too long")

    it "should has correct length", ->
      user.password = "[email protected]"
      expect(user.validPassword()).toBeTruthy()
      expect(user.error_message).toBe("")

Our code above we write to test on User class which have attribue and have attribue name, email, password and methods to check validation on those attribue which check if empty, invalid format, invalid length. In this code we also use jasmine functions, expectations, and matchers such as: describe, beforeEach, expect, toBe, toEqual, toBeTruthy, not., toContain which more information about all of those methods you can find through here

Now let run our test by start the rails server

rails s

And then we can check our code test through link

localhost:3000/jasmine

And then you should see our jasmine report with error. Screenshot from 2015-12-26 08-52-21.png

Let fix our test to make it green

Let create file users.coffee in side directory app/assets/javascripts and write some code as follow here:

class @User
  constructor: (name, email, password) ->
    @name = name.trim()
    @email = email.trim()
    @password = password.trim()
    @error_message = ""
  validName: ->
    if @name == ''
      @error_message = "User name is empty"
      return false
    else if @name.length < 3
      @error_message = "User name too sort"
      return false
    else if @name.length > 16
      @error_message = "User name too long"
      return false
    else
      unless /^[a-zA-Z0-9_-]{3,16}$/.test(@name)
        @error_message = "Invalid User Name"
        return false
    @error_message = ""
    true
  validEmail: ->
    if @email == ''
      @error_message = "Email is empty"
      return false
    else if @email.length > 50
      @error_message = "Email is too long"
      return false
    else
      unless /^[\w+\-.]+@[a-z\d\-.]+\.[a-z]+$/i.test(@email)
        @error_message = "Invalid Email"
        return false
    @error_message = ""
    true
  validPassword: ->
    if @password == ''
      @error_message = "Password is empty"
      return false
    else if @password.length < 6
      @error_message = "Password too sort"
      return false
    else if @password.length > 16
      @error_message = "Password too long"
      return false
    else
      unless /^([a-zA-Z0-9@*#]{6,15})$/.test(@password)
        @error_message = "Invalid Password"
        return false
    @error_message = ""
    true

And to make our code accessable in jasmine we need to add require in jasmine spec which locat in app/spec/javascripts/spec.js.coffee to requie some file:

#=require jquery
# =require users
#=require_tree ./

And now let run our test again Screenshot from 2015-12-26 09-41-08.png Now our test are passed.

Let test our code when it apply on UI

After we successfully create User class for validation user information. Now let test when we apply our code to the form element. To do that we need to have some fixture files. For this article we are going to use two fixture file in app/spec/javascripts/fixture name login.html, signup.html and we need to write html form element inside those file.

<!--login.html-->
<div class="col-md-4 col-md-offset-4 well">
  <legend>Log in</legend>
  <form action="/login">
    <div class="form-group">
      <label for="session_email">Email</label><br>
      <input autofocus="autofocus" class="form-control" type="email" name="session[email]" id="session_email">
      <span class="error"></span>
    </div>

    <div class="form-group">
      <label for="session_password">Password</label><br>
      <input autocomplete="off" class="form-control" type="password" name="session[password]" id="session_password">
    </div>

    <div class="actions">
      <input type="submit" name="commit" value="Log in" class="btn btn-primary">
    </div>
  </form>
</div>
<!--signup.html-->
<div class="col-md-4 col-md-offset-4 well">
  <legend>Sign Up</legend>
  <form class="new_user" id="new_user">
    <div class="form-group">
      <label for="user_name">Name</label><br>
      <input autofocus="autofocus" class="form-control" type="text" name="user[name]" id="user_name">
      <span class="error"></span>
    </div>

    <div class="form-group">
      <label for="user_email">Email</label><br>
      <input class="form-control" type="email" name="user[email]" id="user_email">
      <span class="error"></span>
    </div>

    <div class="form-group">
      <label for="user_password">Password</label><br>
      <input autocomplete="off" class="form-control" type="password" name="user[password]" id="user_password">
      <span class="error"></span>
    </div>

    <div class="form-group">
      <label for="user_confirm_password">Confirm password</label><br>
      <input autocomplete="off" class="form-control" type="password" name="user[password_confirmation]" id="user_password_confirmation">
      <span class="error"></span>
    </div>

    <div class="actions">
      <input type="submit" name="commit" value="Sign Up" class="btn btn-primary">
    </div>
  </form>
</div>

Now let continue writeing our test code inside app/spec/javascrips/users_spec.js.coffee.

......
......
......

describe "Login Form", ->
  beforeEach ->
    loadFixtures "login"

  describe "Email", ->
    it "invalid when user email is incorrect format", ->
      email_field = $('#session_email')
      email_field.validateEmail()
      email_field.val('[email protected]')
      email_field.blur()
      expect(email_field.next('.error')).toHaveText("Invalid Email")
      email_field.val('[email protected]')
      email_field.blur()
      expect(email_field.next('.error')).toHaveText("")

    it "invalid when user email is too long", ->
      email_field = $('#session_email')
      email_field.validateEmail()
      email_field.val('[email protected]')
      email_field.blur()
      expect(email_field.next('.error')).toHaveText("Email is too long")

    it "valid when user email is correct format", ->
      email_field = $('#session_email')
      email_field.validateEmail()
      email_field.val('[email protected]')
      email_field.blur()
      expect(email_field.next('.error')).toHaveText("")

describe "SignUp Form", ->
  beforeEach ->
    loadFixtures "signup"
  describe "Name", ->
    it "invalid when user name is incorect format", ->
      name_field = $('#user_name')
      name_field.validateName()
      name_field.val('name of user')
      name_field.blur()
      expect(name_field.next('.error')).toHaveText("Invalid User Name")

    it "invalid when user name is too long", ->
      name_field = $('#user_name')
      name_field.validateName()
      name_field.val('thelongestnameinthehistory')
      name_field.blur()
      expect(name_field.next('.error')).toHaveText("User name too long")
      name_field.val('rathanakjame')
      name_field.blur()
      expect(name_field.next('.error')).toHaveText("")

  describe "Email", ->
    it "invalid when user email empty", ->
      email_field = $('#user_email')
      email_field.validateEmail()
      email_field.val('')
      email_field.blur()
      expect(email_field.next('.error')).toHaveText("Email is empty")

    it "invalid when user email is incorrect format", ->
      email_field = $('#user_email')
      email_field.validateEmail()
      email_field.val('[email protected]')
      email_field.blur()
      expect(email_field.next('.error')).toHaveText("Invalid Email")

    it "invalid when user email is too long", ->
      email_field = $('#user_email')
      email_field.validateEmail()
      email_field.val('[email protected]')
      email_field.blur()
      expect(email_field.next('.error')).toHaveText("Email is too long")

    it "valid when user email is correct format", ->
      email_field = $('#user_email')
      email_field.validateEmail()
      email_field.val('[email protected]')
      email_field.blur()
      expect(email_field.next('.error')).toHaveText("")

  describe "Password", ->
    it "invalid when user password is sort than 6", ->
      password_field = $('#user_password')
      password_field.validatePassword()
      password_field.val('pass')
      password_field.blur()
      expect(password_field.next('.error')).toHaveText("Password too sort")

    it "invalid when user name is too long", ->
      password_field = $('#user_password')
      password_field.validatePassword()
      password_field.val('thelongestpasswordinthehistory')
      password_field.blur()
      expect(password_field.next('.error')).toHaveText("Password too long")

And then let start run our test code again. Screenshot from 2015-12-26 10-14-24.png

Let fix it

Did you notice on our test code above? Did you see some strange methods .validateName, .validateEmail(), .validatePassword()? these methods we need to create for applying our javascript code into form UI. so let start create them inside app/assets/javascript/users.coffee.

......
......
......

$.fn.validateName = ->
  @each ->
    $(this).blur ->
      user = new User(@value, "", "", "")
      if !user.validName()
        $(this).next('.error').text(user.error_message)
        $(this).addClass('error_form')
      else
        $(this).next('.error').text('')
        $(this).removeClass('error_form')

$.fn.validateEmail = ->
  @each ->
    $(this).blur ->
      user = new User("", @value, "")
      if !user.validEmail()
        $(this).next('.error').text(user.error_message)
        $(this).addClass('error_form')
      else
        $(this).next('.error').text('')
        $(this).removeClass('error_form')

$.fn.validatePassword = ->
  @each ->
    $(this).blur ->
      user = new User("", "", @value)
      if !user.validPassword()
        $(this).next('.error').text(user.error_message)
        $(this).addClass('error_form')
      else
        $(this).next('.error').text('')
        $(this).removeClass('error_form')

Let re-run our test. Screenshot from 2015-12-26 10-31-09.png

Yes, they are all passed.

Applying javascript to app UI

To use our code we just add them to element which we want to apply on Ex:

jQuery ->
  $('#user_name').validateName()
  $('#user_email').validateEmail()
  $('#user_password').validatePassword()

  $('#session_email').validateEmail()

Now are good to go, and let check it out. Screenshot from 2015-12-26 10-39-50.png Screenshot from 2015-12-26 10-40-10.png

Resources:

Source code: https://github.com/RathanakSreang/jasmine-javascript-testing

Conclusion

This is just an example of using jasmine for testing javascript code. So if you aren’t testing your JavaScript so far, now is an excellent time to start. Jasmine's fast and simple syntax makes testing pretty simple. So let's get write test on you code.

In this world there are no food for free.


All Rights Reserved