Project

Exspec

0.0
No commit activity in last 3 years
No release in over 3 years
Don't write specs anymore, just save 'em while testing your code interactively. Specs will become a byproduct.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

Exspec

With Exspec you can write specs while testing your Ruby code interactively in IRB.

Installation

Exspec is available on RubyGems.org: https://rubygems.org/gems/Exspec You can install it by calling

gem install Exspec

Now you should have the exspec command available in your terminal.
Please make sure that your gem's bin folder is included in your PATH variable.

How to Use

In order to run Exspec just call

exspec

in your project directory. If it is a Rails project, Exspec will detect that automatically and start your Rails console. Otherwise you have to load your code manually by calling load code.rb on the exspec IRB console or by creating an exspec.rb file in your project folder and configuring it as shown under Extensions (see below).

Now you can test your code interactively as you are used to do with IRB.

Commands

After testing a part of your code and getting a value back from an instruction, you can use the Exspec commands to specify whether or not you expect this returned value. There are also commands for managing your specs and mocking/stubbing purposes.

Assertions

Exspec provides three ways to specify the expected value of a statement: !expect, !expect_inspect, !assert.

!expect

Calling !expect statement executes the given statement and compares the return value with the value returned by your last executed instruction (or with an exception thrown by your last instruction).

Example:

irb(main):001:0> myStringUtils.concat "Hello", "world!"
=> "Hello world!"
irb(main):002:0> !expect ["Hello", "world!"].join(" ") !# concatenates the params with a space
Successful: concatenates the params with a space
=> "Hello world!"

As you can see, the returned value of the !expect command is the returned value of the given statement. This applies even if the test fails. Therefore, you can continue working with your expected value whether or not you actually got the expected value before, which can be helpful for working with Exspec in TDD (see below, Test-driven Development).

!# comment is optional and can be used after each of the three assertion instructions to describe what you expect. You can also use it stand-alone to add a comment to your spec.

!expect_inspect

Instead of comparing the actual value, !expect_inspect value compares the inspect-value of your last instruction (i.e. the output in IRB or the string returned by calling value.inspect in Ruby) with the given value (which will be treated as a string). Alternatively, you can just call !expect without an argument, which opens a new prompt where you can enter the expected value. By pressing arrow-up on this prompt you automatically get the inspect-value of your last instruction on your input and can change it.

!expect_inspect compares fuzzy. This allows you to omit certain data that can be different when you run the spec next time, for instance time values.

Example:

irb(main):003:0> User.create :name => "test"
=> #<User id: 7, name: "test", created_at: "2013-03-06 04:28:35", updated_at: "2013-03-06 04:28:35", deleted_at: nil>
irb(main):004:0> !expect_inspect #<User id:, name: "test", created_at: "", updated_at: "", deleted_at: nil>
Successful (fuzzy): inspecting last value has yielded "#<User id: 7, name: "test", created_at: "2013-03-06 04:28:35", updated_at: "2013-03-06 04:28:35", deleted_at: nil>"
=> #<User id: 7, name: "test", created_at: "2013-03-06 04:28:35", updated_at: "2013-03-06 04:28:35", deleted_at: nil>

!assert

!assert statement allows you to write any assertions you can express with your code. It executes the statement and passes if the result evaluates to true.

Example:

irb(main):005:0> list = MyList.new
irb(main):006:0> list.add "value"
irb(main):007:0> !assert list.length == 1
Successful: list.length == 1
irb(main):008:0> !assert list.include? "value"
Successful: list.include? "value"

Managing Specs

Your spec gets recorded in your spec history. You can see your current history with

!history

In order to remove an entry from your history you can either use !erase to erase your last instruction or !erase# index, where index is the index of the instruction you want to remove. When you remove the last one, Exspecs also sets your current value (_) back to the return value of your new last instruction. This ensures that you have a consistent spec flow at any point.

If you are done testing a feature, you can save the spec by calling

!save a short description !# more detailled explanation

The !# comment is optional. The above command saves the spec in your spec folder as a_short_description.rb. If you have not specified a test directory (as shown under Extensions, see below), your specs will be saved under test/exspec in your project folder. Now your history is empty again and ready for writing your next spec based on the previous one. If you save the next spec, it will be saved in a folder called a_short_description under your test folder. And so on...

In case your spec is independent from the one before (i.e. it does not need any values from the one before) call !independent or !up and Exspec will load the spec preceding your last saved spec.

To load a spec you can call !load spec name. This searches for the given spec under your currently loaded spec. Preceding the spec name with a dot causes Exspec to search for the given spec in the root of your test directory. You can also load successive specs by joining their names with dots:

!load . first spec . second spec

Calling !specs gives you a list of the specs under your current spec or under the spec specified (!specs . for specs in your test dir root). You can load one of these by using !load# index, where index is the index of a spec in the menu returned by !specs. In the same way works !stack which gives you your currently loaded spec stack from the first spec to the spec you are currently working on.

Similarly, you can use !run name / !run# index or !include name / !include# index to run another spec within your current spec. While !run actually runs the other spec and by that copies its history, !include only includes a pointer to the other spec into your history.

In addition to showing the menus, commands like !history and !specs return the corresponding objects, i.e. an array of spec objects in case of !specs. To keep your history clean this gets returned into another context though, called Exspec context. This will be indicated by a !_: ... output besides your regular IRB => ... output after calling a menu command. You can access these objects and work on the Exspec context by prefix your instructions with an exclamation mark. So you can run arbitrary Ruby code without dirtying your spec. Thus you can also load a spec by calling ! _[0].load after invoking !specs.

Mocking

Exspec provides a very ease to use yet powerful way to writing mocks / stubs. Calling !mock or !stub (which are synonyms in Exspec) gives you an Exspec Mock object or assigns it to a variable when you specify one by calling !mock var_name. A mock keeps track of all method calls on it and returns a new mock for each non-defined method. Also, it saves values that get defined to an attribute of the mock and returns it on accessing the same attribute method later.

For stubbing particular methods you can define those on your mock as follows:

irb(main):009:0> !mock myMock
irb(main):010:0> myMock._def(:add_three) { |param| param + 3 }
irb(main):011:0> myMock.add_three 10
=> 13

The list of params passed to the block is the list of params the method gets called with. When a block is given to the method call, the block is going the be the last parameter.

There are multiple ways for writing an assertion on a mock. The following listing shows some examples:

  • !assert myMock._have_been_called? :add_three
  • !assert myMock._times_called(:add_three) == 1
  • !assert myMock._first_call(:add_three)[:args][0] == 10

A mock also provides methods to access its child mocks returned by calling an undefined method (_child(:method_name)), to access all method calls on that object (_method_calls) and to get the values assigned to its attributes (_attribute[:attr_name]).

Test-driven development

Besides testing existing code Exspec is also well-suited for test-driven development. Just write your spec as normal (see above) until one of your assertions fails. Now you can use the !retry command to rerun your spec to this point. If you have set up Exspec to load your code automatically in the setup_context callback as shown below (see Extensions), Exspec will reload your code by calling !retry. So it is possible to change your code and rerun your spec over and over again until your assertion passes. Then proceed writing your spec until you reach the next failed assertion.

Running specs

To run your specs call exspec from the terminal with the name of your spec, the directory you want to run or just a dot in order to run all your specs:

exspec .

Extensions

For extending Exspec or configuring it for your project you can write an Exspec etension and save it as exspec.rb under your project folder. An Exspec extension is a Ruby Module which extends Exspec::Extension. This provides several callbacks / extension points in order to customize Exspec for you project. Please let me know if you need another extension point and I am going to add it if appropriate.

Example:

require "exspec"

module MyAppExspecExtension
  extend Exspec::Extension

  @@dir = File.dirname(__FILE__)

  config do |config|
    config[:test_dir] = File.expand_path("my_specs/exspec", @@dir)
  end

  setup_context do
    Dir[File.expand_path("myCodeDir/*.rb", @@dir)].each { |file| load file }
  end
 
  execute_command do |command, param_string, options|
    case command # custom commands
      when "hello" # call it with `!hello world`
        execute(command, parameters(param_string), options) do |params|
          value = "Hello #{params[0]}!"
          commit "!hello #{param_string}", value # return to IRB
        end
    end
  end
end