├── .github └── workflows │ └── ruby.yml ├── .gitignore ├── Gemfile ├── README.md ├── Rakefile ├── kintama.gemspec ├── lib ├── kintama.rb └── kintama │ ├── assertions.rb │ ├── context.rb │ ├── mocha.rb │ ├── no_conflict.rb │ ├── reporter.rb │ ├── runnable.rb │ ├── runner.rb │ └── test.rb └── test ├── integration ├── automatic_running_test.rb └── line_based_running_test.rb ├── reporters ├── base_reporter_test.rb ├── inline_reporter_test.rb └── verbose_reporter_test.rb ├── test_helper.rb ├── unit ├── assertions_test.rb ├── context_test.rb ├── runner_test.rb └── test_and_subcontext_access_test.rb └── usage ├── 01_basic_usage_test.rb ├── 02_setup_test.rb ├── 03_teardown_test.rb ├── 04_pending_tests_test.rb ├── 05_aliases_test.rb ├── 06_defining_methods_in_tests_test.rb ├── 07_exceptions_test.rb ├── 08_start_and_finish_test.rb ├── 09_expectations_and_mocking_test.rb ├── 10_let_and_subject_test.rb ├── 11_matcher_test.rb └── 12_action_test.rb /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | name: Ruby 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | ruby: ['2.4', '2.5', '2.6', '2.7', '3.0', '3.1'] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/cache@v2 14 | with: 15 | path: vendor/bundle 16 | key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} 17 | restore-keys: | 18 | ${{ runner.os }}-gems- 19 | - uses: actions/setup-ruby@v1 20 | with: 21 | ruby-version: ${{ matrix.ruby }} 22 | - name: Build and test with Rake 23 | run: | 24 | gem install bundler 25 | bundle config path vendor/bundle 26 | bundle install --jobs 4 --retry 3 27 | bundle exec rake 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/* 2 | doc/* 3 | .DS_Store 4 | dogfood.rb 5 | Gemfile.lock 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Hello 2 | ===== 3 | 4 | This is a tool for testing code. Or maybe it's a tool for exploring ways to test code. See below. 5 | 6 | Huh? Really? Another one? 7 | ==== 8 | 9 | ... Yeah, I know. To be honest, I'm not 100% sure why I'm doing this. Here are some guesses though: 10 | 11 | My testing tools of choice, at the moment, are [Test::Unit][] with [shoulda][] to provide nested contexts, but not really it's macros. 12 | 13 | I'm not a huge fan of [Test::Unit][]. Whenever I've tried to extend its behaviour I've hit snags, and found its code difficult to understand (particularly as lots of it don't seem to be regularly used - I'm looking at you, [TkRunner][] and friends). I also don't really love [RSpec][], but I think that's just a personal preference (I learned with test/unit, and I didn't want to relearn all of the matcher stuff). 14 | 15 | I like [shoulda][], because like [RSpec][], it lets me nest groups of tests in ways that help remove duplication in setups. However, [I don't have a lot of confidence that shoulda is going to stick around in its current, useful-for-stuff-that-isnt-RSpec form](http://robots.thoughtbot.com/post/701863189/shoulda-rails3-and-beyond). 16 | 17 | I like some of the more verbose output that [Cucumber][] and [RSpec][] produce, but as I mentioned above, I don't care for [RSpec][]'s matcher-heavy syntax. It's basically impossible to reproduce that output on anything that uses [Test::Unit][] as a base (see [MonkeySpecDoc][] for an example, which fails because it cannot support any more than one level of nesting) 18 | 19 | I also like things like [`before(:all)`][before_all], and [`fast_context`][fast_context], but don't like having to hack around inside [Test::Unit][] to implement them (I already have with [`test_startup`][test_startup]; it works but who knows for how long). 20 | 21 | 22 | Related work 23 | ------------ 24 | 25 | In the spirit of [shoulda][], a small library called [context][] adds the simple nested context structures to [Test::Unit][], but that's the problem - we can't build anything on top of [Test::Unit][]. 26 | 27 | Ditto for [contest][]. 28 | 29 | Probably the closest thing I've seen is [baretest][]. If you look around the code, some of the implementation details are quite similar to those that have evolved in this code (context-ish objects with parents). However, in many ways baretest is more complex, and the final API that it provides is quite foreign compared to [shoulda][]. 30 | 31 | Another alternative test framework is [riot][], which claims to be fast, but also appears to constrain the way that tests are written by avoiding instance variables in setups, for example. 32 | 33 | [Testy][] is interesting - it looks like its output is YAML! [Tryouts][] is thinking outside the box, using comment examples. 34 | 35 | [Zebra][] addresses the apparent duplication of the test name and the test body, but does it by introducing an [RSpec][]-esque method on every object. Wild. Also, it's an extension of [Test::Unit][], so that's strike two for me, personally. 36 | 37 | I have no idea what to make of [Shindo][]. 38 | 39 | [Exemplor][]... oh my god why am I contributing to this mess. 40 | 41 | Erm. 42 | 43 | Exploring future testing 44 | ------------------------ 45 | 46 | I wanted to explore how easy it would be to reproduce a test framework with a modern, [shoulda][]/RSpec-esque syntax, but that was simple enough to be understandable when anyone needed to change it. 47 | 48 | I also wanted to be able to start exploring different ways of expressing test behaviour, outside of the classic `setup -> test -> teardown` cycle, but didn't feel that I could use test/unit as a basis for this kind of speculative work without entering a world of pain. 49 | 50 | Hence... _this_. 51 | 52 | 53 | Examples 54 | ======== 55 | 56 | These will all be very familiar to most people who are already users of [shoulda][]: 57 | 58 | require 'kintama' 59 | 60 | context "A thing" do 61 | setup do 62 | @thing = Thing.new 63 | end 64 | should "act like a thing" do 65 | assert_equal "thingish", @thing.nature 66 | end 67 | end 68 | 69 | Simple, right? Note that we don't need an outer subclass of `Minitest::Test` or `Test::Unit::TestCase`; it's nice to lose that noise, but otherwise so far so same-old-same-old. That's kind-of the point. Anyway, here's what you get when you run this: 70 | 71 | A thing 72 | should act like a thing: F 73 | 74 | 1 tests, 1 failures 75 | 76 | 1) A thing should act like a thing: 77 | uninitialized constant Thing (at ./examples/simple.rb:6) 78 | 79 | Firstly, it's formatted nicely. There are no cryptic line numbers or `bind` references like [shoulda][]. If you run it from a terminal, you'll get colour output too. That's nice. 80 | 81 | 82 | Aliases 83 | ---- 84 | 85 | There are a bunch of aliases you can use in various ways. If you don't like: 86 | 87 | context "A thing" do 88 | 89 | you could also write: 90 | 91 | describe Thing do # like RSpec! ... 92 | given "a thing" do # ... 93 | testcase "a thing" do # ... 94 | 95 | It's trivial to define other aliases that might make your tests more readable. Similarly for defining the tests themselves, instead of: 96 | 97 | should "act like a thing" do 98 | 99 | you might prefer: 100 | 101 | it "should act like a thing" do # ... 102 | test "acts like a thing" do # ... 103 | 104 | Sometimes just having that flexibility makes all the difference. 105 | 106 | 107 | Setup, teardown, nested contexts 108 | -------------- 109 | 110 | These work as you'd expect based on shoulda or RSpec: 111 | 112 | given "a Thing" do 113 | setup do 114 | @thing = Thing.new 115 | end 116 | 117 | it "should be happy" do 118 | assert @thing.happy? 119 | end 120 | 121 | context "that is prodded" do 122 | setup do 123 | @thing.prod! 124 | end 125 | 126 | should "not be happy" do 127 | assert_false @thing.happy? 128 | end 129 | end 130 | 131 | teardown do 132 | @thing.cleanup_or_something 133 | end 134 | end 135 | 136 | You can also add (several) global `setup` and `teardown` blocks, which will be run before (or after) every test. For example: 137 | 138 | Kintama.setup do 139 | @app = ThingApp.new 140 | end 141 | 142 | given "a request" do 143 | it "should work" do 144 | assert_equal 200, @app.response.status 145 | end 146 | end 147 | 148 | 149 | Helpers 150 | ------- 151 | 152 | If you want to make methods available in your tests, you can define them thusly: 153 | 154 | context "my face" do 155 | should "be awesome" do 156 | assert_equal "awesome", create_face.status 157 | end 158 | 159 | def create_face 160 | Face.new(:name => "james", :eyes => "blue", :something => "something else") 161 | end 162 | end 163 | 164 | Your other options are including a module: 165 | 166 | module FaceHelper 167 | def create_face 168 | # etc ... 169 | end 170 | end 171 | 172 | context "my face" do 173 | include FaceHelper 174 | should "be awesome" do 175 | assert_equal "awesome", create_face.status 176 | end 177 | end 178 | 179 | Or, if you're going to use the method in all your tests, you can add the module globally: 180 | 181 | Kintama.include FaceHelper 182 | 183 | 184 | Extending 185 | --------- 186 | 187 | If you want to add behaviour to Kintama itself (rather than to tests), 188 | you can use extend: 189 | 190 | module Doing 191 | def doing(&block) 192 | @doing = block 193 | end 194 | 195 | def should_change(&block) 196 | doing_block = @doing 197 | should "change something" do 198 | previous_value = instance_eval(&block) 199 | instance_eval(&doing_block) 200 | subsequent_value = instance_eval(&block) 201 | assert subsequent_value != previous_value, "it didn't change" 202 | end 203 | end 204 | 205 | def expect(name, &block) 206 | doing_block = @doing 207 | test "expects #{name}" do 208 | instance_eval(&block) 209 | instance_eval(&doing_block) 210 | end 211 | end 212 | end 213 | 214 | class Thing 215 | attr_reader :total 216 | def initialize 217 | @total = 0 218 | end 219 | def increment 220 | @total += 1 221 | end 222 | end 223 | 224 | context "Given something" do 225 | extend Doing 226 | 227 | setup { @thing = Thing.new } 228 | 229 | doing { @thing.increment } 230 | 231 | should_change { @thing.total } 232 | 233 | # NOTE: this assumes Mocha is present 234 | expect("increment to be called} { @thing.expects(:increment) } 235 | 236 | # etc... 237 | end 238 | 239 | As you can see, extending makes the method available at the context 240 | level, so it can be used to construct tests, new context types, and so 241 | on. This is really where the exciting stuff is. 242 | 243 | Oh, and of course, behaviour can be added to all tests as above: 244 | 245 | Kintama.extend Doing 246 | 247 | 248 | And now, the more experimental stuff 249 | ==================================== 250 | 251 | Wouldn't it be nice to be able to introspect a failed test without having to re-run it? Well, you can. Lets imagine this test: 252 | 253 | context "A thing" do 254 | setup do 255 | @thing = Thing.new 256 | end 257 | should "act like a thing" do 258 | assert_equal "thingish", @thing.nature 259 | end 260 | end 261 | 262 | Well... TO BE CONTINUED. 263 | 264 | 265 | 266 | [Test::Unit]: http://ruby-doc.org/stdlib/libdoc/test/unit/rdoc/ 267 | [TkRunner]: http://ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit/UI/Tk/TestRunner.html 268 | [RSpec]: http://rspec.info 269 | [Cucumber]: http://cukes.info 270 | [MonkeySpecDoc]: http://jgre.org/2008/09/03/monkeyspecdoc/ 271 | [before_all]: http://rspec.info/documentation/ 272 | [fast_context]: https://github.com/lifo/fast_context 273 | [test_startup]: https://github.com/freerange/test_startup 274 | [shoulda]: https://github.com/thoughtbot/shoulda 275 | [baretest]: https://github.com/apeiros/baretest 276 | [riot]: https://github.com/thumblemonks/riot 277 | [context]: https://github.com/jm/context 278 | [contest]: https://github.com/citrusbyte/contest 279 | [Testy]: https://github.com/ahoward/testy 280 | [Tryouts]: https://github.com/delano/tryouts 281 | [Zebra]: https://github.com/jamesgolick/zebra 282 | [Shindo]: https://github.com/geemus/shindo 283 | [Exemplor]: https://github.com/quackingduck/exemplor 284 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "rubygems/package_task" 3 | require "rdoc/task" 4 | 5 | require "rake/testtask" 6 | Rake::TestTask.new do |t| 7 | t.libs << "test" 8 | t.test_files = FileList["test/**/*_test.rb"] 9 | t.verbose = true 10 | end 11 | 12 | 13 | task :default => ["test"] 14 | 15 | # This builds the actual gem. For details of what all these options 16 | # mean, and other ones you can add, check the documentation here: 17 | # 18 | # http://rubygems.org/read/chapter/20 19 | # 20 | spec = Gem::Specification.new do |s| 21 | 22 | # Change these as appropriate 23 | s.name = "kintama" 24 | s.version = "0.2" 25 | s.summary = "It's for writing tests." 26 | s.author = "James Adam" 27 | s.email = "james@lazyatom.com" 28 | s.homepage = "http://github.com/lazyatom" 29 | 30 | s.extra_rdoc_files = %w(README.md) 31 | s.rdoc_options = %w(--main README.md) 32 | 33 | # Add any extra files to include in the gem 34 | s.files = %w(README.md) + Dir.glob("{test,lib}/**/*") 35 | s.require_paths = ["lib"] 36 | 37 | # If you want to depend on other gems, add them here, along with any 38 | # relevant versions 39 | # s.add_dependency("some_other_gem", "~> 0.1.0") 40 | 41 | # If your tests use any gems, include them here 42 | s.add_development_dependency("mocha", ">= 1.11.2") 43 | s.add_development_dependency("rake") 44 | s.add_development_dependency("minitest") 45 | end 46 | 47 | # This task actually builds the gem. We also regenerate a static 48 | # .gemspec file, which is useful if something (i.e. GitHub) will 49 | # be automatically building a gem for this project. If you're not 50 | # using GitHub, edit as appropriate. 51 | # 52 | # To publish your gem online, install the 'gemcutter' gem; Read more 53 | # about that here: http://gemcutter.org/pages/gem_docs 54 | Gem::PackageTask.new(spec) do |pkg| 55 | pkg.gem_spec = spec 56 | end 57 | 58 | desc "Build the gemspec file #{spec.name}.gemspec" 59 | task :gemspec do 60 | file = File.dirname(__FILE__) + "/#{spec.name}.gemspec" 61 | File.open(file, "w") {|f| f << spec.to_ruby } 62 | end 63 | 64 | # If you don't want to generate the .gemspec file, just remove this line. Reasons 65 | # why you might want to generate a gemspec: 66 | # - using bundler with a git source 67 | # - building the gem without rake (i.e. gem build blah.gemspec) 68 | # - maybe others? 69 | task :package => :gemspec 70 | 71 | # Generate documentation 72 | RDoc::Task.new do |rd| 73 | rd.main = "README.md" 74 | rd.rdoc_files.include("README.md", "lib/**/*.rb") 75 | rd.rdoc_dir = "rdoc" 76 | end 77 | 78 | desc 'Clear out RDoc and generated packages' 79 | task :clean => [:clobber_rdoc, :clobber_package] do 80 | rm "#{spec.name}.gemspec" 81 | end 82 | 83 | desc 'Tag the repository in git with gem version number' 84 | task :tag => [:gemspec, :package] do 85 | if `git diff --cached`.empty? 86 | if `git tag`.split("\n").include?("v#{spec.version}") 87 | raise "Version #{spec.version} has already been released" 88 | end 89 | `git add #{File.expand_path("../#{spec.name}.gemspec", __FILE__)}` 90 | `git commit -m "Released version #{spec.version}"` 91 | `git tag v#{spec.version}` 92 | `git push --tags` 93 | `git push` 94 | else 95 | raise "Unstaged changes still waiting to be committed" 96 | end 97 | end 98 | 99 | desc "Tag and publish the gem to rubygems.org" 100 | task :publish => :tag do 101 | `gem push pkg/#{spec.name}-#{spec.version}.gem` 102 | end 103 | -------------------------------------------------------------------------------- /kintama.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # stub: kintama 0.2 ruby lib 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "kintama".freeze 6 | s.version = "0.2" 7 | 8 | s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= 9 | s.require_paths = ["lib".freeze] 10 | s.authors = ["James Adam".freeze] 11 | s.date = "2020-07-10" 12 | s.email = "james@lazyatom.com".freeze 13 | s.extra_rdoc_files = ["README.md".freeze] 14 | s.files = ["README.md".freeze, "lib/kintama".freeze, "lib/kintama.rb".freeze, "lib/kintama/assertions.rb".freeze, "lib/kintama/context.rb".freeze, "lib/kintama/mocha.rb".freeze, "lib/kintama/no_conflict.rb".freeze, "lib/kintama/reporter.rb".freeze, "lib/kintama/runnable.rb".freeze, "lib/kintama/runner.rb".freeze, "lib/kintama/test.rb".freeze, "test/integration".freeze, "test/integration/automatic_running_test.rb".freeze, "test/integration/line_based_running_test.rb".freeze, "test/reporters".freeze, "test/reporters/base_reporter_test.rb".freeze, "test/reporters/inline_reporter_test.rb".freeze, "test/reporters/verbose_reporter_test.rb".freeze, "test/test_helper.rb".freeze, "test/unit".freeze, "test/unit/assertions_test.rb".freeze, "test/unit/context_test.rb".freeze, "test/unit/runner_test.rb".freeze, "test/unit/test_and_subcontext_access_test.rb".freeze, "test/usage".freeze, "test/usage/01_basic_usage_test.rb".freeze, "test/usage/02_setup_test.rb".freeze, "test/usage/03_teardown_test.rb".freeze, "test/usage/04_pending_tests_test.rb".freeze, "test/usage/05_aliases_test.rb".freeze, "test/usage/06_defining_methods_in_tests_test.rb".freeze, "test/usage/07_exceptions_test.rb".freeze, "test/usage/08_start_and_finish_test.rb".freeze, "test/usage/09_expectations_and_mocking_test.rb".freeze, "test/usage/10_let_and_subject_test.rb".freeze, "test/usage/11_matcher_test.rb".freeze, "test/usage/12_action_test.rb".freeze] 15 | s.homepage = "http://github.com/lazyatom".freeze 16 | s.rdoc_options = ["--main".freeze, "README.md".freeze] 17 | s.rubygems_version = "3.0.3".freeze 18 | s.summary = "It's for writing tests.".freeze 19 | 20 | if s.respond_to? :specification_version then 21 | s.specification_version = 4 22 | 23 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 24 | s.add_development_dependency(%q.freeze, [">= 1.11.2"]) 25 | s.add_development_dependency(%q.freeze, [">= 0"]) 26 | s.add_development_dependency(%q.freeze, [">= 0"]) 27 | else 28 | s.add_dependency(%q.freeze, [">= 1.11.2"]) 29 | s.add_dependency(%q.freeze, [">= 0"]) 30 | s.add_dependency(%q.freeze, [">= 0"]) 31 | end 32 | else 33 | s.add_dependency(%q.freeze, [">= 1.11.2"]) 34 | s.add_dependency(%q.freeze, [">= 0"]) 35 | s.add_dependency(%q.freeze, [">= 0"]) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/kintama.rb: -------------------------------------------------------------------------------- 1 | require "ostruct" 2 | require "optparse" 3 | 4 | module Kintama 5 | autoload :Runnable, 'kintama/runnable' 6 | autoload :Context, 'kintama/context' 7 | autoload :Test, 'kintama/test' 8 | autoload :TestFailure, 'kintama/test' 9 | autoload :Reporter, 'kintama/reporter' 10 | autoload :Runner, 'kintama/runner' 11 | autoload :Assertions, 'kintama/assertions' 12 | 13 | class << self 14 | def no_conflict? 15 | ENV["KINTAMA_NO_CONFLICT"] 16 | end 17 | 18 | def reset 19 | @default_context = Class.new(Runnable) 20 | @default_context.send(:include, Kintama::Context) 21 | end 22 | 23 | def default_context 24 | reset unless instance_variable_defined?(:@default_context) && @default_context 25 | @default_context 26 | end 27 | 28 | # Create a new context. Aliases are 'testcase' and 'describe' 29 | def context(name, parent=default_context, &block) 30 | default_context.context(name, parent, &block) 31 | end 32 | alias_method :testcase, :context 33 | alias_method :describe, :context 34 | 35 | # Create a new context starting with "given " 36 | def given(name, parent=default_context, &block) 37 | default_context.given(name, parent, &block) 38 | end 39 | 40 | # Add a setup which will run at the start of every test. 41 | def setup(&block) 42 | default_context.setup(&block) 43 | end 44 | 45 | # Add a teardown which will be run at the end of every test. 46 | def teardown(&block) 47 | default_context.teardown(&block) 48 | end 49 | 50 | def on_start(&block) 51 | default_context.on_start(&block) 52 | end 53 | alias_method :before_all, :on_start 54 | 55 | def on_finish(&block) 56 | default_context.on_finish(&block) 57 | end 58 | alias_method :after_all, :on_finish 59 | 60 | # Makes behaviour available within tests: 61 | # 62 | # module SomeModule 63 | # def blah 64 | # end 65 | # end 66 | # Kintama.include SomeModule 67 | # 68 | # Any methods will then be available within setup, teardown or tests. 69 | def include(mod) 70 | default_context.send(:include, mod) 71 | end 72 | 73 | # Make new testing behaviour available for the definition of tests. 74 | # Methods included in this way are available during the definition of tests. 75 | def extend(mod) 76 | default_context.extend(mod) 77 | end 78 | 79 | def options 80 | @options ||= begin 81 | options = OpenStruct.new( 82 | :reporter => Kintama::Reporter.default, 83 | :runner => Kintama::Runner.default 84 | ) 85 | cmd_options = OptionParser.new do |opts| 86 | opts.banner = "Usage: ruby [options]" 87 | 88 | opts.separator "" 89 | opts.separator "Specific options:" 90 | 91 | opts.on("-r", "--reporter NAME", 92 | "Use the given reporter (inline or verbose)") do |reporter| 93 | options.reporter = Kintama::Reporter.called(reporter) 94 | end 95 | opts.on("-l", "--line LINE", 96 | "Run the test or context on the given line") do |line| 97 | options.runner = Kintama::Runner::Line.new(line) 98 | end 99 | opts.on_tail("-h", "--help", "Show this message") do 100 | puts opts 101 | exit 102 | end 103 | end 104 | cmd_options.parse!(ARGV) 105 | options 106 | end 107 | end 108 | 109 | # Adds the hook to automatically run all known tests using #run when 110 | # ruby exits; this is most useful when running a test file from the command 111 | # line or from within an editor 112 | def add_exit_hook 113 | return if instance_variable_defined?(:@__added_exit_hook) 114 | at_exit { exit(options.runner.with(Kintama.default_context).run(options.reporter) ? 0 : 1) } 115 | @__added_exit_hook = true 116 | end 117 | 118 | # Tries to determine whether or not this is a sensible situation to automatically 119 | # run all tests when ruby exits. At the moment, this is true when either: 120 | # - the test was run via rake 121 | # - the test file was run as the top-level ruby script 122 | # 123 | # This method will always return false if the environment variable 124 | # KINTAMA_EXPLICITLY_DONT_RUN is not nil. 125 | def should_run_on_exit? 126 | return false if ENV["KINTAMA_EXPLICITLY_DONT_RUN"] 127 | return test_file_was_run? || run_via_rake? 128 | end 129 | 130 | private 131 | 132 | def test_file_was_run? 133 | caller.last.split(":").first == $0 134 | end 135 | 136 | def run_via_rake? 137 | caller.find { |line| File.basename(line.split(":").first) == "rake_test_loader.rb" } != nil 138 | end 139 | end 140 | end 141 | 142 | unless Kintama.no_conflict? 143 | [:context, :given, :describe, :testcase].each do |method| 144 | unless self.respond_to?(method) 145 | eval %|def #{method}(*args, &block); Kintama.#{method}(*args, &block); end| 146 | end 147 | end 148 | end 149 | 150 | Kintama.add_exit_hook if Kintama.should_run_on_exit? 151 | -------------------------------------------------------------------------------- /lib/kintama/assertions.rb: -------------------------------------------------------------------------------- 1 | require "set" 2 | require "stringio" 3 | 4 | module Kintama 5 | module Assertions 6 | def assert(expression, message="failed") 7 | raise Kintama::TestFailure, message unless expression 8 | end 9 | 10 | def flunk(message="flunked.") 11 | assert false, message 12 | end 13 | 14 | def assert_equal(expected, actual, message="Expected #{expected.inspect} but got #{actual.inspect}") 15 | assert actual == expected, message 16 | end 17 | 18 | def assert_not_equal(expected, actual, message="Expected #{expected.inspect} to not be equal to #{actual.inspect}") 19 | assert actual != expected, message 20 | end 21 | 22 | def assert_nil(object, message="#{object.inspect} was not nil") 23 | assert_equal nil, object, message 24 | end 25 | 26 | def assert_not_nil(object, message="should not be nil") 27 | assert_not_equal nil, object, message 28 | end 29 | 30 | def assert_match(regexp, string, message="expected #{string.inspect} to match #{regexp.inspect}") 31 | assert (string =~ regexp), message 32 | end 33 | 34 | def assert_no_match(regexp, string, message="expected #{string.inspect} not to match #{regexp.inspect}") 35 | assert !(string =~ regexp), message 36 | end 37 | 38 | def assert_kind_of(klass, thing, message="should be a kind of #{klass}") 39 | assert thing.is_a?(klass), message 40 | end 41 | 42 | def assert_same(expected, actual, message="Expected #{expected.inspect} (oid=#{expected.object_id}) to be the same as #{actual.inspect} (oid=#{actual.object_id})") 43 | assert actual.equal?(expected), message 44 | end 45 | 46 | def assert_same_elements(expected, object, message = "#{object.inspect} does not contain the same elements as #{expected.inspect}") 47 | assert Set.new(expected) == Set.new(object), message 48 | end 49 | 50 | def assert_nothing_raised(message="should not raise anything", &block) 51 | yield 52 | rescue Exception => e 53 | raise Kintama::TestFailure, message + " (#{e} was raised)" 54 | end 55 | 56 | def assert_raises(klass_or_message=Exception, message="should raise an exception", &block) 57 | if klass_or_message.respond_to?(:ancestors) 58 | klass = klass_or_message 59 | else 60 | message = klass_or_message 61 | klass = Exception 62 | end 63 | yield 64 | raised = false 65 | rescue => e 66 | if e.class.ancestors.include?(klass) 67 | raised = true 68 | else 69 | raised = false 70 | end 71 | ensure 72 | raise Kintama::TestFailure, message unless raised 73 | end 74 | 75 | def assert_output(expected, message="Expected output to match #{expected.inspect}", &block) 76 | output = capture_output(&block).read.strip 77 | if expected.is_a?(Regexp) 78 | assert_match expected, output, message 79 | else 80 | assert_equal expected, output, message 81 | end 82 | end 83 | 84 | def assert_not_output(not_expected, message="Expected output not to match #{not_expected.inspect}", &block) 85 | output = capture_output(&block).read.strip 86 | if not_expected.is_a?(Regexp) 87 | assert_no_match not_expected, output, message 88 | else 89 | assert_not_equal not_expected, output, message 90 | end 91 | end 92 | 93 | private 94 | 95 | def capture_output(&block) 96 | out = StringIO.new 97 | $stdout = out 98 | yield 99 | out.rewind 100 | return out 101 | ensure 102 | $stdout = STDOUT 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/kintama/context.rb: -------------------------------------------------------------------------------- 1 | module Kintama 2 | module Context 3 | def setup 4 | (setup_blocks + after_setup_blocks).each do |block| 5 | instance_eval(&block) 6 | end 7 | end 8 | 9 | def teardown 10 | blocks = teardown_blocks 11 | blocks.each do |block| 12 | instance_eval(&block) 13 | end 14 | end 15 | 16 | def setup_blocks 17 | context = self.class 18 | blocks = context.setup_blocks 19 | while(context.superclass.respond_to?(:setup_blocks)) 20 | context = context.superclass 21 | blocks.unshift(*context.setup_blocks) 22 | end 23 | blocks 24 | end 25 | 26 | def after_setup_blocks 27 | context = self.class 28 | blocks = context.after_setup_blocks 29 | while(context.superclass.respond_to?(:after_setup_blocks)) 30 | context = context.superclass 31 | blocks.unshift(*context.after_setup_blocks) 32 | end 33 | blocks 34 | end 35 | 36 | def teardown_blocks 37 | context = self.class 38 | blocks = context.teardown_blocks 39 | while(context.superclass.respond_to?(:teardown_blocks)) 40 | context = context.superclass 41 | blocks.push(*context.teardown_blocks) 42 | end 43 | blocks 44 | end 45 | 46 | def self.included(base) 47 | base.extend(ClassMethods) 48 | end 49 | 50 | module ClassMethods 51 | 52 | def find_definition_1_8 53 | line = caller.find { |l| l =~ /^[^:]+:(\d+)$/ } 54 | if line 55 | parts = line.split(":") 56 | parts[1] = parts[1].to_i 57 | parts 58 | end 59 | end 60 | 61 | def find_definition_yarv(&block) 62 | block.source_location if block 63 | end 64 | 65 | def find_definition_rbx(&block) 66 | if block 67 | block_environment = block.block 68 | [block_environment.file, block_environment.line] 69 | end 70 | end 71 | 72 | def find_definition(&block) 73 | if defined? RUBY_ENGINE 74 | case RUBY_ENGINE 75 | when "ruby", "jruby" 76 | Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('1.9') ? find_definition_yarv(&block) : find_definition_1_8 77 | when "rbx" 78 | find_definition_rbx(&block) 79 | end 80 | else 81 | find_definition_1_8 82 | end 83 | end 84 | 85 | # Create a new context. If this is called within a context, a new subcontext 86 | # will be created. Aliases are 'testcase' and 'describe' 87 | def context(name=nil, parent=self, &block) 88 | c = Class.new(parent) 89 | c.send(:include, Kintama::Context) 90 | c.name = name.to_s if name 91 | c.definition = find_definition(&block) 92 | c.class_eval(&block) if block 93 | c 94 | end 95 | alias_method :testcase, :context 96 | alias_method :describe, :context 97 | 98 | # Create a new context starting with "given " 99 | def given(name, parent=self, &block) 100 | context("given " + name, parent, &block) 101 | end 102 | 103 | def setup_blocks 104 | @setup_blocks ||= [] 105 | end 106 | 107 | def after_setup_blocks 108 | @after_setup_blocks ||= [] 109 | end 110 | 111 | def teardown_blocks 112 | @teardown_blocks ||= [] 113 | end 114 | 115 | # Define the setup for this context. 116 | # It will also be run for any subcontexts, before their own setup blocks 117 | def setup(&block) 118 | self.setup_blocks << block 119 | end 120 | 121 | def after_setup(&block) 122 | self.after_setup_blocks << block 123 | end 124 | alias_method :action, :after_setup 125 | 126 | # Define the teardown for this context. 127 | # It will also be run for any subcontexts, after their own teardown blocks 128 | def teardown(&block) 129 | self.teardown_blocks << block 130 | end 131 | 132 | def on_start_blocks 133 | @on_start_blocks ||= [] 134 | end 135 | 136 | def on_start(&block) 137 | self.on_start_blocks << block 138 | end 139 | alias_method :before_all, :on_start 140 | 141 | def on_finish_blocks 142 | @on_finish_blocks ||= [] 143 | end 144 | 145 | def on_finish(&block) 146 | self.on_finish_blocks << block 147 | end 148 | alias_method :after_all, :on_finish 149 | 150 | def let(name, &block) 151 | define_method(name) do 152 | memo = "@__#{name}" 153 | if instance_variable_defined?(memo) 154 | instance_variable_get(memo) 155 | else 156 | instance_variable_set(memo, instance_eval(&block)) 157 | end 158 | end 159 | end 160 | 161 | # Defines the subject of any matcher-based tests. 162 | def subject(&block) 163 | let("subject", &block) 164 | end 165 | 166 | # Define a test to run in this context. 167 | def test(name, &block) 168 | c = Class.new(self) 169 | c.send(:include, Kintama::Test) 170 | c.name = name 171 | c.definition = find_definition(&block) 172 | c.block = block if block_given? 173 | end 174 | 175 | # Define a test to run in this context. The test name will start with "should " 176 | # You can either supply a name and block, or a matcher. In the latter case, a test 177 | # will be generated using that matcher. 178 | def should(name_or_matcher, &block) 179 | if name_or_matcher.respond_to?(:matches?) 180 | test("should " + name_or_matcher.description) do 181 | assert name_or_matcher.matches?(subject), name_or_matcher.failure_message 182 | end 183 | else 184 | test("should " + name_or_matcher, &block) 185 | end 186 | end 187 | 188 | # Define a test using a negated matcher, e.g. 189 | # 190 | # subject { 'a' } 191 | # should_not equal('b') 192 | # 193 | def should_not(matcher) 194 | test("should not " + matcher.description) do 195 | assert !matcher.matches?(subject), matcher.negative_failure_message 196 | end 197 | end 198 | 199 | # Define a test to run in this context. The test name will start with "it " 200 | def it(name, &block) 201 | test("it " + name, &block) 202 | end 203 | 204 | def inherited(child) 205 | children << child 206 | end 207 | 208 | def children 209 | @children ||= [] 210 | end 211 | 212 | def tests 213 | children.select { |c| c.is_a_test? }.sort_by { |t| t.name } 214 | end 215 | 216 | def subcontexts 217 | children.select { |c| c.is_a_context? }.sort_by { |s| s.name } 218 | end 219 | 220 | def all_runnables 221 | tests + subcontexts + subcontexts.map { |s| s.all_runnables }.flatten 222 | end 223 | 224 | # Returns true if this context has no known failed tests. 225 | def passed? 226 | failures.empty? 227 | end 228 | 229 | # Returns an array of tests in this and all subcontexts which failed in 230 | # the previous run 231 | def failures 232 | ran_tests.select { |t| !t.passed? } + subcontexts.map { |s| s.failures }.flatten 233 | end 234 | 235 | def pending 236 | ran_tests.select { |t| t.pending? } + subcontexts.map { |s| s.pending }.flatten 237 | end 238 | 239 | def [](name) 240 | subcontexts.find { |s| s.name == name } || tests.find { |t| t.name == name } 241 | end 242 | 243 | # Runs all tests in this context and any subcontexts. 244 | # Returns true if all tests passed; otherwise false 245 | def run(reporter=nil) 246 | run_tests(tests, true, reporter) 247 | end 248 | 249 | # Run a specific set of tests using the given the reporter 250 | def run_tests(test_set, run_subcontexts, reporter) 251 | @ran_tests = [] 252 | reporter.context_started(self) if reporter 253 | on_start_blocks.each { |b| instance_eval(&b) } 254 | test_set.each { |t| instance = t.new; instance.run(reporter); ran_tests << instance } 255 | subcontexts.each { |s| s.run(reporter) } if run_subcontexts 256 | on_finish_blocks.each { |b| instance_eval(&b) } 257 | reporter.context_finished(self) if reporter 258 | passed? 259 | end 260 | 261 | def runnable_on_line(line) 262 | known_runnables = all_runnables.delete_if { |r| r.line_defined.nil? } 263 | sorted_runnables = known_runnables.sort_by { |r| r.line_defined } 264 | if sorted_runnables.first && line >= sorted_runnables.first.line_defined 265 | next_runnable = sorted_runnables.find { |r| r.line_defined > line } 266 | index = sorted_runnables.index(next_runnable) 267 | if index != nil && index > 0 268 | sorted_runnables[index-1] 269 | else 270 | sorted_runnables.last 271 | end 272 | else 273 | nil 274 | end 275 | end 276 | 277 | private 278 | 279 | def ran_tests 280 | @ran_tests || [] 281 | end 282 | end 283 | end 284 | end 285 | -------------------------------------------------------------------------------- /lib/kintama/mocha.rb: -------------------------------------------------------------------------------- 1 | require 'kintama' 2 | require 'mocha/api' 3 | 4 | module Kintama::Mocha 5 | module Expect 6 | def expect(name, &block) 7 | context do 8 | setup(&block) 9 | test("expect " + name) {} 10 | end 11 | end 12 | end 13 | 14 | def self.setup 15 | Kintama.include Mocha::API 16 | Kintama.include Mocha::Hooks 17 | Kintama.extend(Kintama::Mocha::Expect) 18 | 19 | Kintama.setup do 20 | mocha_setup 21 | end 22 | Kintama.teardown do 23 | begin 24 | mocha_verify 25 | rescue Mocha::ExpectationError => e 26 | raise e 27 | ensure 28 | mocha_teardown 29 | end 30 | end 31 | end 32 | end 33 | 34 | Kintama::Mocha.setup 35 | -------------------------------------------------------------------------------- /lib/kintama/no_conflict.rb: -------------------------------------------------------------------------------- 1 | ENV["KINTAMA_NO_CONFLICT"] = "true" 2 | require "kintama" 3 | -------------------------------------------------------------------------------- /lib/kintama/reporter.rb: -------------------------------------------------------------------------------- 1 | module Kintama 2 | class Reporter 3 | 4 | def self.default 5 | colour = $stdin.tty? 6 | Verbose.new(colour) 7 | end 8 | 9 | def self.called(name) 10 | case name.to_s 11 | when /verbose/i 12 | default 13 | when /inline/i 14 | Inline.new 15 | else 16 | default 17 | end 18 | end 19 | 20 | class Base 21 | attr_reader :runner, :test_count 22 | 23 | def initialize(*args) 24 | @test_count = 0 25 | end 26 | 27 | def started(runner) 28 | @runner = runner 29 | @start = Time.now 30 | end 31 | 32 | def context_started(context) 33 | end 34 | 35 | def context_finished(context) 36 | end 37 | 38 | def test_started(test) 39 | @test_count += 1 40 | end 41 | 42 | def test_finished(test) 43 | end 44 | 45 | def finished 46 | @duration = Time.now - @start 47 | end 48 | 49 | def test_summary 50 | output = ["#{@test_count} tests", "#{runner.failures.length} failures"] 51 | output << "#{runner.pending.length} pending" if runner.pending.any? 52 | output.join(", ") + " (#{format("%.4f", @duration)} seconds)" 53 | end 54 | 55 | def show_results 56 | puts test_summary 57 | puts "\n" + failure_messages.join("\n\n") if runner.failures.any? 58 | end 59 | 60 | def failure_messages 61 | x = 0 62 | runner.failures.map do |test| 63 | x += 1 64 | "#{x}) #{test.full_name}:\n #{test.failure_message}" 65 | end 66 | end 67 | 68 | def character_status_of(test) 69 | if test.pending? 70 | 'P' 71 | elsif test.passed? 72 | '.' 73 | else 74 | 'F' 75 | end 76 | end 77 | end 78 | 79 | class Inline < Base 80 | def test_finished(test) 81 | print character_status_of(test) 82 | end 83 | 84 | def show_results 85 | puts 86 | super 87 | end 88 | end 89 | 90 | class Verbose < Base 91 | INDENT = " " 92 | 93 | def initialize(colour=false) 94 | super() 95 | @colour = colour 96 | @current_indent_level = 0 97 | end 98 | 99 | def indent 100 | INDENT * @current_indent_level 101 | end 102 | 103 | def context_started(context) 104 | unless context == Kintama.default_context || context.name == nil 105 | print indent + context.name + "\n" if context.name 106 | @current_indent_level += 1 107 | end 108 | end 109 | 110 | def context_finished(context) 111 | unless context == Kintama.default_context || context.name == nil 112 | @current_indent_level -= 1 113 | puts if @current_indent_level == 0 && context != runner.runnables.last 114 | end 115 | end 116 | 117 | def test_started(test) 118 | super 119 | print indent + test.name + ": " unless @colour 120 | end 121 | 122 | def test_finished(test) 123 | if @colour 124 | puts coloured_name(test) 125 | else 126 | puts character_status_of(test) 127 | end 128 | end 129 | 130 | private 131 | 132 | def coloured_name(test) 133 | test_name = indent + test.name 134 | if test.pending? 135 | yellow(test_name) 136 | elsif test.passed? 137 | green(test_name) 138 | else 139 | red(test_name) 140 | end 141 | end 142 | 143 | def colour(text, colour_code) 144 | "#{colour_code}#{text}\e[0m" 145 | end 146 | 147 | def green(text) 148 | colour(text, "\e[32m") 149 | end 150 | 151 | def red(text) 152 | colour(text, "\e[31m") 153 | end 154 | 155 | def yellow(text) 156 | colour(text, "\e[33m") 157 | end 158 | end 159 | 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /lib/kintama/runnable.rb: -------------------------------------------------------------------------------- 1 | module Kintama 2 | class Runnable 3 | class << self 4 | attr_accessor :name, :definition 5 | 6 | def to_s 7 | "<#{is_a_test? ? 'Test' : 'Context'}:#{name}>" 8 | end 9 | 10 | def is_a_test? 11 | ancestors.index(Kintama::Test) && 12 | ancestors.index(Kintama::Test) < ancestors.index(Kintama::Context) 13 | end 14 | 15 | def is_a_context? 16 | !is_a_test? 17 | end 18 | 19 | def parent 20 | superclass.ancestors.include?(Kintama::Context) ? superclass : nil 21 | end 22 | 23 | # Returns the full name of this context, taking any parent contexts into account 24 | def full_name 25 | if instance_variable_defined?(:@name) && @name 26 | [parent ? parent.full_name : nil, @name].compact.join(" ") 27 | else 28 | nil 29 | end 30 | end 31 | 32 | def line_defined 33 | definition ? definition.last : nil 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/kintama/runner.rb: -------------------------------------------------------------------------------- 1 | module Kintama 2 | class Runner 3 | 4 | def self.default 5 | Default.new 6 | end 7 | 8 | class Base 9 | attr_reader :runnables 10 | 11 | def initialize 12 | @runnables = [] 13 | end 14 | 15 | def with(*runnables) 16 | @runnables = runnables 17 | self 18 | end 19 | 20 | def run(reporter=Kintama::Reporter.default) 21 | reporter.started(self) 22 | @ran_runnables = run_tests(reporter) 23 | reporter.finished 24 | reporter.show_results 25 | passed? 26 | end 27 | 28 | def passed? 29 | failures.empty? 30 | end 31 | 32 | def failures 33 | @ran_runnables.map { |r| r.failures }.flatten 34 | end 35 | 36 | def pending 37 | @ran_runnables.map { |r| r.pending }.flatten 38 | end 39 | end 40 | 41 | # Runs every test provided as part of the constructor 42 | class Default < Base 43 | def run_tests(reporter) 44 | @runnables.each do |r| 45 | r.run(reporter) 46 | end 47 | @runnables 48 | end 49 | end 50 | 51 | # Runs only the test or context which contains the provided line 52 | class Line < Base 53 | def initialize(line) 54 | @line = line.to_i 55 | end 56 | 57 | def run_tests(reporter) 58 | runnable = @runnables.map { |r| r.runnable_on_line(@line) }.compact.first 59 | if runnable 60 | if runnable.is_a_test? 61 | hierarchy = [] 62 | parent = runnable.parent.parent 63 | until parent == Kintama.default_context do 64 | hierarchy.unshift parent 65 | parent = parent.parent 66 | end 67 | hierarchy.each { |context| reporter.context_started(context) } 68 | runnable.parent.run_tests([runnable], false, reporter) 69 | hierarchy.reverse.each { |context| reporter.context_finished(context) } 70 | [runnable.parent] 71 | else 72 | runnable.run(reporter) 73 | [runnable] 74 | end 75 | else 76 | puts "Nothing runnable found on line #{@line}" 77 | exit(-1) 78 | end 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/kintama/test.rb: -------------------------------------------------------------------------------- 1 | module Kintama 2 | class TestFailure < StandardError; end 3 | 4 | module Test 5 | include Kintama::Assertions 6 | 7 | def self.included(base) 8 | class << base 9 | attr_accessor :block 10 | 11 | def pending? 12 | !instance_variable_defined?(:@block) || @block.nil? 13 | end 14 | 15 | def run 16 | new.run 17 | end 18 | end 19 | base.send :attr_reader, :failure 20 | end 21 | 22 | def run(reporter=nil) 23 | @failure = nil 24 | reporter.test_started(self) if reporter 25 | unless pending? 26 | begin 27 | setup 28 | instance_eval(&self.class.block) 29 | rescue Exception => e 30 | @failure = e 31 | ensure 32 | begin 33 | teardown 34 | rescue Exception => e 35 | @failure ||= e 36 | end 37 | end 38 | end 39 | reporter.test_finished(self) if reporter 40 | passed? 41 | end 42 | 43 | def pending? 44 | self.class.pending? 45 | end 46 | 47 | def passed? 48 | @failure.nil? 49 | end 50 | 51 | def name 52 | self.class.name 53 | end 54 | 55 | def full_name 56 | self.class.full_name 57 | end 58 | 59 | def failure_message 60 | "#{@failure.message}\n#{failure_backtrace}" 61 | end 62 | 63 | def failure_backtrace 64 | base_dir = File.expand_path("../..", __FILE__) 65 | @failure.backtrace.select { |line| File.expand_path(line).index(base_dir).nil? }.map { |l| " "*4 + File.expand_path(l) }.join("\n") 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /test/integration/automatic_running_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AutomaticRunningTest < KintamaIntegrationTest 4 | 5 | def test_should_be_able_to_run_kintama_tests_automatically_when_file_is_loaded 6 | test_with_content(%{ 7 | context "given a thing" do 8 | should "work" do 9 | assert true 10 | end 11 | end 12 | }).run.should_have_passing_exit_status 13 | 14 | test_with_content(%{ 15 | context "given a thing" do 16 | should "not work" do 17 | flunk 18 | end 19 | end 20 | }).run.should_have_failing_exit_status 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/integration/line_based_running_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class LineBasedRunningTest < KintamaIntegrationTest 4 | def test_should_be_able_to_run_the_test_by_giving_the_line_number_the_test_is_defined_on 5 | test = test_with_content(%{ 6 | context "given a thing" do 7 | should "run this test" do 8 | assert true 9 | end 10 | should "not run this test" do 11 | flunk 12 | end 13 | end 14 | }) 15 | test.run('--line 3') do 16 | assert_output(/^#{passing("should run this test")}\n\n1 tests/) 17 | assert_output(/^1 tests, 0 failures/) 18 | end 19 | test.run('--line 6') do 20 | assert_output(/^#{failing("should not run this test")}\n\n1 tests/) 21 | assert_output(/^1 tests, 1 failures/) 22 | end 23 | end 24 | 25 | def test_should_be_able_to_run_the_test_by_giving_the_line_number_within_the_test_definition 26 | test = test_with_content(%{ 27 | context "given a thing" do 28 | should "run this test" do 29 | assert true 30 | end 31 | should "not run this test" do 32 | flunk 33 | end 34 | end 35 | }) 36 | test.run('--line 4') do 37 | assert_output(/^#{passing("should run this test")}\n\n1 tests/) 38 | end 39 | test.run('--line 7') do 40 | assert_output(/^#{failing("should not run this test")}\n\n1 tests/) 41 | end 42 | end 43 | 44 | def test_should_be_able_to_run_all_tests_within_a_context_when_line_falls_on_a_context 45 | test_with_content(%{ 46 | context "given a thing" do 47 | should "not run this test" do 48 | flunk 49 | end 50 | context "and another thing" do 51 | should "run this test" do 52 | end 53 | should "run this test too" do 54 | end 55 | end 56 | end 57 | }).run('--line 6') do 58 | assert_output(/#{passing("should run this test")}\n#{passing("should run this test too")}\n\n2 tests/) 59 | end 60 | end 61 | 62 | def test_should_be_able_to_run_a_test_defined_in_a_second_top_level_context 63 | test_with_content(%{ 64 | context "given a thing" do 65 | should "not run this test" do 66 | flunk 67 | end 68 | end 69 | context "and another thing" do 70 | should "run this test" do 71 | end 72 | end 73 | }).run('--line 8') do 74 | assert_output(/#{passing("should run this test")}\n\n1 tests/) 75 | end 76 | end 77 | 78 | def test_should_print_out_the_full_nested_test_name 79 | test_with_content(%{ 80 | context "given a test" do 81 | context "that is nested deeply" do 82 | should "print the full nesting name" do 83 | end 84 | end 85 | end 86 | }).run('--line 5') do 87 | assert_output(/given a test\n that is nested deeply\n/) 88 | end 89 | end 90 | 91 | def test_should_not_show_pending_tests_in_the_same_context_as_pending_when_not_targeted 92 | test_with_content(%{ 93 | context "given a context with a pending test" do 94 | should "only show the run test" do 95 | end 96 | should "ignore the pending test" 97 | end 98 | }).run('--line 3') do 99 | refute_output(/1 pending/) 100 | end 101 | end 102 | 103 | def test_should_be_able_to_target_a_top_level_context 104 | end 105 | 106 | def test_should_run_all_tests_when_context_is_on_target_line 107 | test_with_content(%{ 108 | context "given a context with a pending test" do 109 | should "run this" do 110 | end 111 | should "run this too" do 112 | end 113 | end 114 | }).run('--line 2') do 115 | assert_output(/2 tests/) 116 | end 117 | end 118 | 119 | def test_should_report_if_nothing_runnable_can_be_found_for_that_line 120 | test_with_content(%{ 121 | context "given a short context" do 122 | should "not run this" do 123 | end 124 | end 125 | }).run('--line 1') do 126 | assert_output(/Nothing runnable found on line 1/) 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /test/reporters/base_reporter_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class BaseReporterTest < KintamaIntegrationTest 4 | report_with Kintama::Reporter::Base 5 | 6 | def test_assert_output_works 7 | assert_output("yes\n") do 8 | puts "yes" 9 | end 10 | end 11 | 12 | def test_should_print_summary_when_a_test_passes 13 | context "given something" do 14 | should "pass" do 15 | assert true 16 | end 17 | end. 18 | should_output("1 tests, 0 failures") 19 | end 20 | 21 | def test_should_print_out_summary_when_multiple_tests_pass 22 | context "given something" do 23 | should "pass" do 24 | assert true 25 | end 26 | should "also pass" do 27 | assert true 28 | end 29 | end. 30 | should_output("2 tests, 0 failures") 31 | end 32 | 33 | def test_should_print_out_summary_when_a_pending_test_exists 34 | context "given something" do 35 | should "pass" do 36 | assert true 37 | end 38 | should "not be implemented yet" 39 | end. 40 | should_output("2 tests, 0 failures, 1 pending") 41 | end 42 | 43 | def test_should_print_out_failure_details_if_tests_fail 44 | context "given something" do 45 | should "fail" do 46 | flunk 47 | end 48 | should "pass" do 49 | assert true 50 | end 51 | end. 52 | should_output(%{ 53 | 1) given something should fail: 54 | flunked 55 | }) 56 | end 57 | 58 | def test_should_print_out_the_test_duration 59 | context "given something" do 60 | should "pass" do 61 | assert true 62 | end 63 | end. 64 | should_output(/^1 tests, 0 failures \(0\.\d+ seconds\)/) 65 | end 66 | 67 | def test_should_print_out_the_names_of_tests_that_fail 68 | context "given something" do 69 | should "fail" do 70 | flunk 71 | end 72 | end. 73 | should_output(%{ 74 | 1) given something should fail: 75 | flunked 76 | }) 77 | end 78 | 79 | def test_should_include_line_in_test_of_error_in_failure_message 80 | context "given a test that fails" do 81 | should "report line of failing test" do 82 | $line = __LINE__; flunk 83 | end 84 | end. 85 | should_output(/#{Regexp.escape(File.expand_path(__FILE__))}:#{$line}/) 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /test/reporters/inline_reporter_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class InlineReporterTest < KintamaIntegrationTest 4 | report_with Kintama::Reporter::Inline 5 | 6 | def test_should_print_out_dots_when_a_test_passes 7 | context "given something" do 8 | should "pass" do 9 | assert true 10 | end 11 | end. 12 | should_output(%{ 13 | . 14 | }) 15 | end 16 | 17 | def test_should_print_out_many_dots_as_tests_run 18 | context "given something" do 19 | should "pass" do 20 | assert true 21 | end 22 | should "also pass" do 23 | assert true 24 | end 25 | end. 26 | should_output(%{ 27 | .. 28 | }) 29 | end 30 | 31 | def test_should_print_out_Fs_as_tests_fail 32 | context "given something" do 33 | should "fail" do 34 | flunk 35 | end 36 | should "pass" do 37 | assert true 38 | end 39 | end. 40 | should_output(%{ 41 | F. 42 | }) 43 | end 44 | 45 | def test_should_print_out_Ps_for_pending_tests 46 | context "given something" do 47 | should "not be implemented yet" 48 | should "pass" do 49 | assert true 50 | end 51 | end. 52 | should_output(%{ 53 | P. 54 | }) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/reporters/verbose_reporter_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class VerboseReporterTest < KintamaIntegrationTest 4 | report_with Kintama::Reporter::Verbose 5 | 6 | def test_should_print_out_test_names 7 | context "given something" do 8 | should "pass" do 9 | assert true 10 | end 11 | should "pass too" do 12 | assert true 13 | end 14 | end. 15 | should_output(%{ 16 | given something 17 | should pass: . 18 | should pass too: . 19 | }) 20 | end 21 | 22 | def test_should_print_out_Ps_beside_pending_test_names 23 | context "given something" do 24 | should "not be implemented" 25 | should "pass" do 26 | assert true 27 | end 28 | end. 29 | should_output(%{ 30 | given something 31 | should not be implemented: P 32 | should pass: . 33 | }) 34 | end 35 | 36 | def test_should_nest_printed_context_and_test_names 37 | context "given something" do 38 | should "pass" do 39 | assert true 40 | end 41 | context "and something else" do 42 | should "pass" do 43 | assert true 44 | end 45 | end 46 | context "and then this" do 47 | should "also pass" do 48 | assert true 49 | end 50 | end 51 | end. 52 | should_output(%{ 53 | given something 54 | should pass: . 55 | and something else 56 | should pass: . 57 | and then this 58 | should also pass: . 59 | }) 60 | end 61 | 62 | def test_should_print_out_a_summary_of_the_failing_tests_if_some_fail 63 | context "given something" do 64 | should "fail" do 65 | assert 1 == 2, "1 should equal 2" 66 | end 67 | end. 68 | should_output(%{ 69 | given something should fail: 70 | 1 should equal 2 71 | }) 72 | end 73 | 74 | def test_should_print_out_a_summary_of_the_failing_tests_if_an_exception_occurs_in_a_test 75 | context "given something" do 76 | should "fail" do 77 | raise "unexpected issue!" 78 | end 79 | end. 80 | should_output(%{ 81 | given something should fail: 82 | unexpected issue! 83 | }) 84 | end 85 | 86 | def test_should_print_out_a_summary_of_the_failing_tests_if_a_nested_test_fails 87 | context "given something" do 88 | context "and something else" do 89 | should "fail" do 90 | assert 1 == 2, "1 should equal 2" 91 | end 92 | end 93 | end. 94 | should_output(%{ 95 | given something and something else should fail: 96 | 1 should equal 2 97 | }) 98 | end 99 | 100 | def test_should_treat_a_context_as_transparent_if_it_has_no_name 101 | context "given something" do 102 | context do 103 | should "pass" do 104 | assert true 105 | end 106 | end 107 | end. 108 | should_output(%{ 109 | given something 110 | should pass: . 111 | }) 112 | end 113 | 114 | def test_should_print_out_test_names_in_colour_if_colour_is_set 115 | use_colour = true 116 | use_reporter Kintama::Reporter::Verbose.new(use_colour) 117 | 118 | context "given tests reported in colour" do 119 | should "show failures in red" do 120 | flunk 121 | end 122 | should "show passes in green" do 123 | assert true 124 | end 125 | should "show pending tests in yellow" 126 | end. 127 | should_output(%{ 128 | given tests reported in colour 129 | \e\[31m should show failures in red\e\[0m 130 | \e\[32m should show passes in green\e\[0m 131 | \e\[33m should show pending tests in yellow\e\[0m 132 | }) 133 | end 134 | 135 | def test_should_print_appropriate_test_names_when_given_and_it_aliases_are_used 136 | context "In a world without hope" do 137 | given "a massive gun" do 138 | it "should work out well in the end" do 139 | assert true 140 | end 141 | end 142 | end. 143 | should_output(%{ 144 | In a world without hope 145 | given a massive gun 146 | it should work out well in the end: . 147 | }) 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) 2 | require 'bundler/setup' 3 | require 'minitest/autorun' 4 | 5 | ENV["KINTAMA_EXPLICITLY_DONT_RUN"] = "true" 6 | require 'kintama' 7 | 8 | require 'stringio' 9 | require 'mocha/minitest' 10 | 11 | class Minitest::Test 12 | def setup 13 | Kintama.reset 14 | end 15 | 16 | private 17 | 18 | module ::Kernel 19 | def capture_stdout 20 | out = StringIO.new 21 | $stdout = out 22 | yield 23 | out.rewind 24 | return out 25 | ensure 26 | $stdout = STDOUT 27 | end 28 | 29 | def silence_stdout 30 | $stdout = StringIO.new 31 | return yield 32 | ensure 33 | $stdout = STDOUT 34 | end 35 | end 36 | 37 | def assert_output(expected, &block) 38 | output = capture_stdout(&block).read 39 | if expected.is_a?(Regexp) 40 | assert_match expected, output 41 | else 42 | assert_equal expected, output 43 | end 44 | end 45 | end 46 | 47 | class KintamaIntegrationTest < Minitest::Test 48 | class << self 49 | def reporter_class 50 | @reporter_class ||= Kintama::Reporter::Verbose 51 | end 52 | 53 | def report_with(reporter_class) 54 | @reporter_class = reporter_class 55 | end 56 | end 57 | 58 | attr_reader :reporter 59 | 60 | private 61 | 62 | def use_reporter(reporter) 63 | @reporter = reporter 64 | end 65 | 66 | def context(name, &block) 67 | ContextTestRunner.new(Kintama.context(name, &block), self) 68 | end 69 | 70 | def running_default_context(name, &block) 71 | Kintama.context(name, &block) 72 | ContextTestRunner.new(Kintama.default_context, self) 73 | end 74 | 75 | class ContextTestRunner 76 | def initialize(context, test_unit_test) 77 | @test_unit_test = test_unit_test 78 | @context = context 79 | @result = nil 80 | use_colour = false 81 | @reporter = @test_unit_test.reporter || test_unit_test.class.reporter_class.new(use_colour) 82 | @output = capture_stdout do 83 | @result = Kintama::Runner.default.with(context).run(@reporter) 84 | end.read 85 | end 86 | 87 | def should_output(expected_output) 88 | if expected_output.is_a?(Regexp) 89 | processed_output = expected_output 90 | else 91 | processed_output = deindent_string_argument(expected_output) 92 | end 93 | @test_unit_test.assert_match processed_output, @output 94 | self 95 | end 96 | alias_method :and_output, :should_output 97 | 98 | def should_pass(message=nil) 99 | @test_unit_test.assert(@result == true, message || "Expected a pass, but failed: #{@context.failures.map { |f| f.failure.message }.join(", ")}") 100 | self 101 | end 102 | alias_method :and_pass, :should_pass 103 | 104 | def should_fail(message=nil) 105 | @test_unit_test.assert(@result == false, message || "Expected a failure, but passed!") 106 | self 107 | end 108 | alias_method :and_fail, :should_fail 109 | 110 | def with_failure(failure) 111 | @test_unit_test.assert_match deindent_string_argument(failure), @output 112 | self 113 | end 114 | 115 | def should_run_tests(number) 116 | @test_unit_test.assert_equal number, @reporter.test_count, "Expected #{number} tests to run, but #{@reporter.test_count} actually did" 117 | self 118 | end 119 | 120 | private 121 | 122 | def deindent_string_argument(string) 123 | initial_indent = string.gsub(/^\n/, '').match(/^(\s+)/) 124 | initial_indent = initial_indent ? initial_indent[1] : "" 125 | string.gsub("\n#{initial_indent}", "\n").gsub(/^\n/, '').gsub(/\s+$/, '') 126 | end 127 | end 128 | 129 | def test_with_content(content) 130 | IntegrationTestRunner.new(self, content) 131 | end 132 | 133 | class IntegrationTestRunner 134 | def initialize(test_unit_test, test_body) 135 | @test_unit_test = test_unit_test 136 | @test_body = test_body 137 | end 138 | 139 | def run(options = nil, &block) 140 | path = write_test(@test_body) 141 | prev = ENV["KINTAMA_EXPLICITLY_DONT_RUN"] 142 | ENV["KINTAMA_EXPLICITLY_DONT_RUN"] = nil 143 | @output = `ruby #{path} #{options}` 144 | ENV["KINTAMA_EXPLICITLY_DONT_RUN"] = prev 145 | @exit_status = $? 146 | instance_eval(&block) if block_given? 147 | self 148 | end 149 | 150 | def should_have_passing_exit_status 151 | @test_unit_test.assert_equal 0, @exit_status.exitstatus 152 | end 153 | 154 | def should_have_failing_exit_status 155 | @test_unit_test.assert_equal 1, @exit_status.exitstatus 156 | end 157 | 158 | private 159 | 160 | def assert_output(match) 161 | @test_unit_test.assert_match(match, @output) 162 | end 163 | 164 | def refute_output(match) 165 | @test_unit_test.refute_match(match, @output) 166 | end 167 | 168 | def passing(test_name) 169 | if $stdin.tty? 170 | /\e\[32m\s*#{test_name}\e\[0m/ 171 | else 172 | /\s*#{test_name}: ./ 173 | end 174 | end 175 | 176 | def failing(test_name) 177 | if $stdin.tty? 178 | /\e\[31m\s*#{test_name}\e\[0m/ 179 | else 180 | /\s*#{test_name}: F/ 181 | end 182 | end 183 | 184 | def write_test(string) 185 | path = "/tmp/kintama_tmp_test.rb" 186 | File.open(path, "w") do |f| 187 | f.puts %|$LOAD_PATH.unshift "#{File.expand_path("../../lib", __FILE__)}"; require "kintama"| 188 | f.puts string.strip 189 | end 190 | path 191 | end 192 | end 193 | end 194 | -------------------------------------------------------------------------------- /test/unit/assertions_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AssertionsTest < Minitest::Test 4 | 5 | class PseudoTest 6 | include Kintama::Assertions 7 | end 8 | 9 | def setup 10 | @test = PseudoTest.new 11 | end 12 | 13 | def test_should_provide_assert 14 | assert_passed { @test.assert true } 15 | assert_failed("waaa") { @test.assert false, "waaa" } 16 | end 17 | 18 | def test_should_provide_flunk 19 | assert_failed(":(") { @test.flunk ":(" } 20 | end 21 | 22 | def test_should_provide_assert_equal 23 | assert_passed { @test.assert_equal 1, 1 } 24 | assert_failed("blurgh") { @test.assert_equal 1, 2, "blurgh" } 25 | end 26 | 27 | def test_should_provide_assert_not_equal 28 | assert_passed { @test.assert_not_equal 1, 2 } 29 | assert_failed("sadface") { @test.assert_not_equal 1, 1, "sadface" } 30 | end 31 | 32 | def test_should_provide_assert_nil 33 | assert_failed("bums") { @test.assert_nil Object.new, "bums" } 34 | assert_passed { @test.assert_nil nil } 35 | end 36 | 37 | def test_should_provide_assert_not_nil 38 | assert_passed { @test.assert_not_nil Object.new } 39 | assert_failed("fiddlesticks!") { @test.assert_not_nil nil, "fiddlesticks!" } 40 | end 41 | 42 | def test_should_provide_assert_kind_of 43 | assert_passed { @test.assert_kind_of Integer, 1 } 44 | assert_passed { @test.assert_kind_of Object, 1 } 45 | assert_passed { @test.assert_kind_of String, "hello" } 46 | assert_failed("pa!") { @test.assert_kind_of String, 1, "pa!" } 47 | end 48 | 49 | def test_should_provide_assert_nothing_raised 50 | assert_passed { @test.assert_nothing_raised { true } } 51 | assert_passed { @test.assert_nothing_raised { false } } 52 | assert_failed("ouch (oh no was raised)") { @test.assert_nothing_raised("ouch") { raise "oh no" } } 53 | end 54 | 55 | def test_should_provide_assert_raises 56 | assert_passed { @test.assert_raises { raise "urgh" } } 57 | assert_passed { @test.assert_raises(StandardError) { raise StandardError, "urgh" } } 58 | assert_failed("no way") { @test.assert_raises("no way") { false } } 59 | assert_failed { @test.assert_raises(RuntimeError) { raise StandardError, "urgh" } } 60 | assert_passed { @test.assert_raises("woah") { this_method_doesnt_exist } } 61 | end 62 | 63 | def test_should_provide_assert_match 64 | assert_passed { @test.assert_match(/jam/, "bluejam") } 65 | assert_failed(%|expected "blah" to match /mm/|) { @test.assert_match(/mm/, "blah") } 66 | end 67 | 68 | def test_should_provide_assert_no_match 69 | assert_passed { @test.assert_no_match(/jam/, "bluejay") } 70 | assert_failed(%|expected "blah" not to match /ah/|) { @test.assert_no_match(/ah/, "blah") } 71 | end 72 | 73 | def test_should_provide_assert_same_elements_to_compare_arrays 74 | assert_passed { @test.assert_same_elements [1,2,3], [1,2,3] } 75 | assert_passed { @test.assert_same_elements [1,2,3], [3,1,2] } 76 | assert_failed("#{[1,3,4].inspect} does not contain the same elements as #{[1,2,3].inspect}") do 77 | @test.assert_same_elements [1,2,3], [1,3,4] 78 | end 79 | end 80 | 81 | def test_should_provide_assert_same 82 | expected, actual = 'foo', 'foo' 83 | assert_passed { @test.assert_same expected, expected } 84 | assert_failed("Expected #{expected.inspect} (oid=#{expected.object_id}) to be the same as #{actual.inspect} (oid=#{actual.object_id})") do 85 | @test.assert_same expected, actual 86 | end 87 | end 88 | 89 | def test_should_provide_assert_output 90 | assert_passed do 91 | @test.assert_output 'foobar' do 92 | puts 'foobar' 93 | end 94 | end 95 | assert_passed do 96 | @test.assert_output(/oba/) do 97 | puts 'foobar' 98 | end 99 | end 100 | assert_failed('Expected output to match "foobar"') do 101 | @test.assert_output 'foobar' do 102 | puts 'whambam' 103 | end 104 | end 105 | end 106 | 107 | def test_should_provide_assert_not_output 108 | assert_passed do 109 | @test.assert_not_output 'foobar' do 110 | puts 'whambam' 111 | end 112 | end 113 | assert_passed do 114 | @test.assert_not_output(/oba/) do 115 | puts 'whambam' 116 | end 117 | end 118 | assert_failed('Expected output not to match "foobar"') do 119 | @test.assert_not_output 'foobar' do 120 | puts 'foobar' 121 | end 122 | end 123 | end 124 | 125 | private 126 | 127 | def assert_passed 128 | yield 129 | end 130 | 131 | def assert_failed(message=nil) 132 | yield 133 | raise "assertion did not fail!" if failed 134 | rescue Kintama::TestFailure => e 135 | if message 136 | assert_equal message, e.message, "assertion failure message didn't match" 137 | end 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /test/unit/context_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ContextTest < Minitest::Test 4 | def test_should_clear_previous_failure_when_running_test_again 5 | $thing = 456 6 | x = context "Given something" do 7 | should "work" do 8 | assert_equal 123, $thing 9 | end 10 | end 11 | assert_equal false, x.run 12 | $thing = 123 13 | assert_equal true, x.run 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/unit/runner_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class RunnerTest < Minitest::Test 4 | def setup 5 | @reporter = Kintama::Reporter::Verbose.new(colour=false) 6 | end 7 | 8 | def test_should_return_true_if_all_tests_pass 9 | c = context "given something" do 10 | should("pass") { assert true } 11 | should("also pass") { assert true } 12 | end 13 | assert_equal true, silence_stdout { runner(c).run(@reporter) } 14 | end 15 | 16 | def test_should_return_false_if_any_tests_fails 17 | c = context "given something" do 18 | should("pass") { assert true } 19 | should("fail") { flunk } 20 | end 21 | assert_equal false, silence_stdout { runner(c).run(@reporter) } 22 | end 23 | 24 | def test_should_be_able_to_run_tests_from_several_contexts 25 | reporter = stub_reporter 26 | 27 | reporter.expects(:test_started).twice 28 | 29 | c1 = context "given something" do 30 | should "pass" do 31 | assert true 32 | end 33 | end 34 | c2 = context "given another thing" do 35 | should "also pass" do 36 | assert true 37 | end 38 | end 39 | r = runner(c1, c2) 40 | silence_stdout { r.run(reporter) } 41 | end 42 | 43 | def test_should_only_run_each_context_once 44 | c = context "Given something" do 45 | context "and a thing" do 46 | should "only run this once" do 47 | end 48 | end 49 | end 50 | 51 | reporter = stub_reporter 52 | reporter.expects(:context_started).with(responds_with(:name, "and a thing")).once 53 | 54 | silence_stdout { runner(c).run(reporter) } 55 | end 56 | 57 | def test_should_nest_verbose_output_properly_when_running_tests_from_several_contexts 58 | c1 = context "given something" do 59 | should "pass" do 60 | assert true 61 | end 62 | end 63 | c2 = context "given another thing" do 64 | should "also pass" do 65 | assert true 66 | end 67 | end 68 | assert_output(/^given something\n should pass: \.\n\ngiven another thing\n should also pass: \./) do 69 | runner(c1, c2).run(@reporter) 70 | end 71 | end 72 | 73 | private 74 | 75 | def stub_reporter 76 | reporter = stub('reporter') 77 | [:started, :finished, :context_started, :context_finished, 78 | :test_started, :test_finished, :show_results].each do |method| 79 | reporter.stubs(method) 80 | end 81 | reporter 82 | end 83 | 84 | def runner(*args) 85 | Kintama::Runner::Default.new.with(*args) 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /test/unit/test_and_subcontext_access_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TestAndSubcontextAccessTest < Minitest::Test 4 | 5 | def test_should_stash_all_defined_contexts_so_they_can_be_accessed_later 6 | c1 = context "Given some context" do 7 | should "stash this" do 8 | end 9 | end 10 | c2 = context "Given some other context" do 11 | should "also stash this" do 12 | end 13 | end 14 | assert_equal [c1, c2], Kintama.default_context.subcontexts 15 | end 16 | 17 | def test_should_allow_running_of_specific_subcontexts_using_hashlike_syntax 18 | x = context "Given something" do 19 | should "not be run" do 20 | flunk 21 | end 22 | context "and another thing" do 23 | should "pass" do 24 | assert true 25 | end 26 | end 27 | end 28 | inner_context = x["and another thing"] 29 | inner_context.run 30 | assert inner_context.passed? 31 | end 32 | 33 | def test_should_allow_running_of_specific_tests_using_hashlike_syntax 34 | x = context "Given something" do 35 | should "fail when run" do 36 | flunk 37 | end 38 | end 39 | t = x["should fail when run"].new 40 | t.run 41 | assert !t.passed? 42 | end 43 | 44 | def test_should_return_true_if_running_a_subcontext_passes 45 | x = context "Given something" do 46 | context "and another thing" do 47 | should "pass" do 48 | assert true 49 | end 50 | end 51 | end 52 | assert_equal true, x['and another thing'].run 53 | end 54 | 55 | def test_should_return_true_if_running_a_test_passes 56 | x = context "Given something" do 57 | should "pass when run" do 58 | assert true 59 | end 60 | end 61 | assert_equal true, x['should pass when run'].run 62 | end 63 | 64 | def test_should_return_false_if_running_a_subcontext_fails 65 | x = context "Given something" do 66 | context "and another thing" do 67 | should "fail" do 68 | flunk 69 | end 70 | end 71 | end 72 | assert_equal false, x['and another thing'].run 73 | end 74 | 75 | def test_should_return_false_if_running_a_test_fails 76 | x = context "Given something" do 77 | should "fail when run" do 78 | flunk 79 | end 80 | end 81 | assert_equal false, x['should fail when run'].run 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /test/usage/01_basic_usage_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class BasicUsageTest < KintamaIntegrationTest 4 | 5 | def test_should_pass_when_all_tests_pass 6 | context "Given a test that passes" do 7 | should "pass this test" do 8 | assert true 9 | end 10 | 11 | should "pass this test too" do 12 | assert true 13 | end 14 | end. 15 | should_output(%{ 16 | Given a test that passes 17 | should pass this test: . 18 | should pass this test too: . 19 | }). 20 | and_pass 21 | end 22 | 23 | def test_should_fail_when_all_tests_fail 24 | context "Given a test that fails" do 25 | should "fail the test" do 26 | flunk 27 | end 28 | end. 29 | should_output(%{ 30 | Given a test that fails 31 | should fail the test: F 32 | }). 33 | and_fail 34 | end 35 | 36 | def test_should_fail_when_any_tests_fail 37 | context "Given two tests" do 38 | should "pass the passing test" do 39 | assert true 40 | end 41 | 42 | should "ultimately fail because there is one failing test" do 43 | flunk 44 | end 45 | end. 46 | should_run_tests(2). 47 | and_fail. 48 | with_failure("should ultimately fail because there is one failing test") 49 | end 50 | 51 | def test_should_fail_when_any_assertion_within_a_test_fails 52 | context "Given a test with two assertions" do 53 | should "fail because one of the assertions doesn't pass" do 54 | assert 1 == 2 55 | assert true 56 | end 57 | end. 58 | should_run_tests(1). 59 | and_fail 60 | end 61 | 62 | def test_should_not_run_any_code_beyond_a_failing_assertion 63 | context "Given a test with a failure before the end of the test" do 64 | should "not execute any test after the test failures" do 65 | flunk "fail here" 66 | raise "should not get here!" 67 | end 68 | end. 69 | should_fail.with_failure("fail here") 70 | end 71 | 72 | def test_should_allow_nesting_of_contexts 73 | context "Given a context" do 74 | context "and a subcontext" do 75 | should "nest this test within the inner context" do 76 | assert true 77 | end 78 | end 79 | end. 80 | should_output(%{ 81 | Given a context 82 | and a subcontext 83 | should nest this test within the inner context: . 84 | }). 85 | and_pass 86 | end 87 | 88 | def test_should_allow_multiple_subcontexts 89 | context "Given some contexts" do 90 | context "one containing failing tests" do 91 | should "ultimately fail because of the failing test" do 92 | flunk 93 | end 94 | end 95 | 96 | context "one containing passing tests" do 97 | should "still run the passing test" do 98 | assert true 99 | end 100 | end 101 | end. 102 | should_run_tests(2). 103 | and_output(%{ 104 | Given some contexts 105 | one containing failing tests 106 | should ultimately fail because of the failing test: F 107 | one containing passing tests 108 | should still run the passing test: . 109 | }). 110 | and_fail 111 | end 112 | 113 | def test_should_allow_deep_nesting_of_subcontexts 114 | context "Given something" do 115 | context "and another thing" do 116 | context "and one more thing" do 117 | should "work" do 118 | assert true 119 | end 120 | end 121 | end 122 | end. 123 | should_output(%{ 124 | Given something 125 | and another thing 126 | and one more thing 127 | should work: . 128 | }). 129 | and_pass 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /test/usage/02_setup_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SetupTest < KintamaIntegrationTest 4 | 5 | def test_should_allow_setup_to_provide_instance_variables 6 | context "When setup sets an instance variable" do 7 | setup do 8 | @name = "james" 9 | end 10 | 11 | should "provide access to that instance variable in the test" do 12 | assert_equal "james", @name 13 | end 14 | end. 15 | should_run_tests(1). 16 | and_pass 17 | end 18 | 19 | def test_should_call_all_setup_methods_when_running_tests_in_a_nested_context 20 | context "Given a setup block in the outer context" do 21 | setup do 22 | @name = "james" 23 | end 24 | 25 | context "and another setup block in the inner context" do 26 | setup do 27 | @name += " is amazing" 28 | end 29 | 30 | should "run both setup blocks before the test" do 31 | assert_equal "james is amazing", @name 32 | end 33 | end 34 | end. 35 | should_run_tests(1). 36 | and_pass 37 | end 38 | 39 | def test_should_only_run_necessary_setups_where_tests_at_different_nestings_exist 40 | context "Given a setup in the outer context" do 41 | setup do 42 | @name = "james" 43 | end 44 | 45 | context "and another setup in the inner context" do 46 | setup do 47 | @name += " is amazing" 48 | end 49 | 50 | should "run both setups for tests in the inner context" do 51 | assert_equal "james is amazing", @name 52 | end 53 | end 54 | 55 | should "only run the outer setup for tests in the outer context" do 56 | assert_equal "james", @name 57 | end 58 | end. 59 | should_run_tests(2). 60 | and_pass 61 | end 62 | 63 | def test_should_run_setup_defined_on_kintama_itself_before_other_setups 64 | Kintama.setup do 65 | @thing = 'abc' 66 | end 67 | 68 | context "Given a context with a setup block" do 69 | setup do 70 | @thing += ' easy as 123' 71 | end 72 | 73 | should "have run the setup defined in the default behaviour before the context setup" do 74 | assert_equal 'abc easy as 123', @thing 75 | end 76 | end. 77 | should_run_tests(1). 78 | and_pass 79 | end 80 | 81 | def test_should_allow_multiple_setups_to_be_registered 82 | context "Given a context with multiple setup blocks" do 83 | setup do 84 | @name ||= "James" 85 | end 86 | 87 | setup do 88 | @name += " Bond" 89 | end 90 | 91 | should "run them all in order" do 92 | assert_equal "James Bond", @name 93 | end 94 | end. 95 | should_run_tests(1). 96 | and_pass 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /test/usage/03_teardown_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TeardownTest < KintamaIntegrationTest 4 | 5 | def setup 6 | super 7 | @order = sequence('teardown order') 8 | end 9 | attr_reader :order 10 | 11 | def test_should_run_teardown_after_the_test_finishes 12 | spy = teardown_spy 13 | spy.expects(:in_test).once.in_sequence(order) 14 | spy.expects(:tore_down).once.in_sequence(order) 15 | 16 | context "Given a context with a teardown block" do 17 | teardown do 18 | spy.tore_down 19 | end 20 | 21 | should "run teardown after the test runs" do 22 | spy.in_test 23 | end 24 | end 25 | end 26 | 27 | def test_should_run_all_teardowns_in_proximity_of_nesting_order_after_a_nested_test_finishes 28 | spy = teardown_spy 29 | spy.expects(:tore_down).with(:inner).in_sequence(order) 30 | spy.expects(:tore_down).with(:outer).in_sequence(order) 31 | 32 | context "Given a context with a teardown block" do 33 | teardown do 34 | spy.tore_down(:outer) 35 | end 36 | 37 | context "with a subcontext with another teardown block" do 38 | teardown do 39 | spy.tore_down(:inner) 40 | end 41 | 42 | should "run the inner and then outer teardowns after this test" do 43 | end 44 | end 45 | end 46 | end 47 | 48 | def test_should_run_teardown_defined_on_kintama_itself_after_other_teardowns 49 | spy = teardown_spy 50 | spy.expects(:tore_down).with(:context_teardown).in_sequence(order) 51 | spy.expects(:tore_down).with(:kintama_global_teardown).in_sequence(order) 52 | 53 | Kintama.teardown do 54 | spy.tore_down(:kintama_global_teardown) 55 | end 56 | 57 | context "Given a context with a teardown block" do 58 | should "run the context teardown, and then the kintama global teardown" do 59 | end 60 | 61 | teardown do 62 | spy.tore_down(:context_teardown) 63 | end 64 | end 65 | end 66 | 67 | def test_should_allow_multiple_teardowns_to_be_registered 68 | spy = teardown_spy 69 | spy.expects(:tore_down).with(:first_teardown).in_sequence(order) 70 | spy.expects(:tore_down).with(:second_teardown).in_sequence(order) 71 | 72 | context "Given a context with multiple teardown blocks" do 73 | should "run them all in the order they appear" do 74 | assert true 75 | end 76 | 77 | teardown do 78 | spy.tore_down(:first_teardown) 79 | end 80 | 81 | teardown do 82 | spy.tore_down(:second_teardown) 83 | end 84 | end 85 | end 86 | 87 | def test_should_run_teardowns_even_after_exceptions_in_tests 88 | spy = teardown_spy 89 | spy.expects(:tore_down) 90 | 91 | context "Given a test that fails" do 92 | should "still run teardown" do 93 | raise "BOOM" 94 | end 95 | 96 | teardown do 97 | spy.tore_down 98 | end 99 | end 100 | end 101 | 102 | def test_should_not_mask_exceptions_in_tests_with_ones_in_teardown 103 | context "Given a test and teardown that fails" do 104 | should "report the error in the test" do 105 | raise "exception from test" 106 | end 107 | 108 | teardown do 109 | raise "exception from teardown" 110 | end 111 | end. 112 | should_fail. 113 | with_failure("exception from test") 114 | end 115 | 116 | private 117 | 118 | def teardown_spy 119 | stub('teardown spy', tore_down: nil) 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /test/usage/04_pending_tests_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PendingTestsTest < KintamaIntegrationTest 4 | 5 | def test_should_pass_any_pending_tests 6 | context "Given a context with an unimplemented test" do 7 | should "indicate that the test is not implemented" # NOTE - no test body 8 | end. 9 | should_output(%{ 10 | Given a context with an unimplemented test 11 | should indicate that the test is not implemented: P 12 | }). 13 | and_pass 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /test/usage/05_aliases_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AliasesTest < KintamaIntegrationTest 4 | 5 | def test_provides_given_alias_for_context 6 | context "In a kintama test" do 7 | given "a context that is defined using the `given` method" do 8 | test "will be described using `given` in the output" do 9 | assert true 10 | end 11 | end 12 | end. 13 | should_output(%{ 14 | In a kintama test 15 | given a context that is defined using the `given` method 16 | will be described using `given` in the output: . 17 | }) 18 | end 19 | 20 | def test_provides_testcase_alias_for_context 21 | context "In a kintama test" do 22 | testcase "a context defined using `testcase`" do 23 | test "does not prefix anything to the context name" do 24 | assert true 25 | end 26 | end 27 | end. 28 | should_output(%{ 29 | In a kintama test 30 | a context defined using `testcase` 31 | does not prefix anything to the context name: . 32 | }) 33 | end 34 | 35 | def test_provides_describe_alias_for_context 36 | context "In a kintama test" do 37 | describe "a context defined using `describe`" do 38 | test "does not prefix anything to the context name" do 39 | assert true 40 | end 41 | end 42 | end. 43 | should_output(%{ 44 | In a kintama test 45 | a context defined using `describe` 46 | does not prefix anything to the context name: . 47 | }) 48 | end 49 | 50 | def test_provides_should_alias_for_test 51 | context "A context with a test defined using `should`" do 52 | should "output the test with `should` in the name" do 53 | assert true 54 | end 55 | end. 56 | should_output(%{ 57 | A context with a test defined using `should` 58 | should output the test with `should` in the name: . 59 | }) 60 | end 61 | 62 | def test_provides_it_alias_for_test 63 | context "A context with a test defined using `it`" do 64 | it "outputs the test with `it` in the name" do 65 | assert true 66 | end 67 | end. 68 | should_output(%{ 69 | A context with a test defined using `it` 70 | it outputs the test with `it` in the name: . 71 | }) 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /test/usage/06_defining_methods_in_tests_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DefiningMethodsInTestsTest < KintamaIntegrationTest 4 | 5 | def test_should_allow_methods_defined_in_the_context_to_be_called_in_tests 6 | context "Given a context with a method defined in it" do 7 | def do_something 8 | 123 9 | end 10 | 11 | should "allow the method to be called within the test" do 12 | assert self.respond_to?(:do_something) 13 | assert_equal 123, do_something 14 | end 15 | end. 16 | should_run_tests(1). 17 | and_pass 18 | end 19 | 20 | def test_should_allow_methods_defined_in_the_context_to_be_called_in_tests_in_subcontexts 21 | context "Given a method defined in the outer context" do 22 | def do_something 23 | 234 24 | end 25 | 26 | context "in a subcontext" do 27 | should "allow the method to be called" do 28 | assert self.respond_to?(:do_something) 29 | assert_equal 234, do_something 30 | end 31 | end 32 | end. 33 | should_run_tests(1). 34 | and_pass 35 | end 36 | 37 | def test_should_not_allow_methods_from_one_context_to_bleed_into_another 38 | context "Given some subcontexts" do 39 | context "one of which contains a method" do 40 | def do_another_thing 41 | 123 42 | end 43 | 44 | it "should be possible to call the method within the same subcontext" do 45 | assert self.respond_to?(:do_another_thing) 46 | assert_equal 123, do_another_thing 47 | end 48 | end 49 | 50 | context "one of which does not contain that method" do 51 | it "should not be possible to call the method defined in a different context" do 52 | assert !self.respond_to?(:do_another_thing) 53 | assert_raises("should not be able to call this") { do_another_thing } 54 | end 55 | end 56 | end. 57 | should_run_tests(2). 58 | and_pass 59 | end 60 | 61 | module ModuleIncludedInContext 62 | def method_from_module 63 | 456 64 | end 65 | end 66 | 67 | def test_should_be_able_to_call_methods_from_included_modules_in_tests 68 | context "Given a module included into a context" do 69 | include ModuleIncludedInContext 70 | 71 | should "allow calling methods from that module" do 72 | assert_equal 456, method_from_module 73 | end 74 | end. 75 | should_run_tests(1). 76 | and_pass 77 | end 78 | 79 | module ModuleWithMethodReferencingAnInstanceVariable 80 | def method_referencing_an_instance_variable 81 | @thing 82 | end 83 | end 84 | 85 | def test_should_allow_defined_methods_to_refer_to_instance_variables_defined_in_setup_when_included_via_modules 86 | context "Given I define an instance variable in my setup" do 87 | setup do 88 | @thing = 123 89 | end 90 | 91 | include ModuleWithMethodReferencingAnInstanceVariable 92 | 93 | should "be able to call a method that refers to that variable in a test" do 94 | assert_equal 123, method_referencing_an_instance_variable 95 | end 96 | end. 97 | should_run_tests(1). 98 | and_pass "Thing was not defined!" 99 | end 100 | 101 | module ModuleIncludedIntoGlobalScope 102 | def method_included_in_global_scope 103 | 'abc' 104 | end 105 | end 106 | 107 | def test_should_allow_including_default_behaviour_in_all_contexts 108 | Kintama.include ModuleIncludedIntoGlobalScope 109 | context "Given a context" do 110 | should "be able to call a method from the globally shared behaviour" do 111 | assert_equal 'abc', method_included_in_global_scope 112 | end 113 | end. 114 | should_run_tests(1). 115 | and_pass "something was not defined!" 116 | end 117 | 118 | def test_should_be_able_to_compose_shoulds_into_methods 119 | context "Given a context" do 120 | def self.should_create_a_should_from_a_method 121 | should "have created this test" do 122 | assert true 123 | end 124 | end 125 | 126 | should_create_a_should_from_a_method 127 | end. 128 | should_run_tests(1). 129 | and_output(%{ 130 | Given a context 131 | should have created this test: . 132 | }). 133 | and_pass 134 | end 135 | 136 | def test_should_be_able_to_call_methods_in_subcontexts_that_create_tests 137 | context "Given a subcontext" do 138 | def self.with_a_method 139 | should "create this test in the subcontext" do 140 | assert true 141 | end 142 | end 143 | 144 | context "which calls a method defined at the top level" do 145 | with_a_method 146 | end 147 | end. 148 | should_run_tests(1). 149 | and_output(%{ 150 | Given a subcontext 151 | which calls a method defined at the top level 152 | should create this test in the subcontext: . 153 | }). 154 | and_pass 155 | end 156 | 157 | module ModuleToAddBehaviourToContext 158 | def method_to_define_test 159 | should "create this test in the subcontext" do 160 | assert true 161 | end 162 | end 163 | end 164 | 165 | def test_should_be_able_to_call_methods_in_subcontexts_that_create_tests_when_defined_in_modules 166 | context "Given a subcontext" do 167 | extend ModuleToAddBehaviourToContext 168 | 169 | context "which calls a method defined at the top level" do 170 | method_to_define_test 171 | end 172 | end. 173 | should_run_tests(1). 174 | and_output(%{ 175 | Given a subcontext 176 | which calls a method defined at the top level 177 | should create this test in the subcontext: . 178 | }). 179 | and_pass 180 | end 181 | 182 | module ModuleExtendedIntoGlobalScope 183 | def method_to_define_a_test 184 | should "define a test within the top-level-extended module" do 185 | assert true 186 | end 187 | end 188 | end 189 | 190 | def test_should_be_able_to_add_behaviour_to_kintama 191 | Kintama.extend ModuleExtendedIntoGlobalScope 192 | context "A context that isn't explicitly extended by a module" do 193 | method_to_define_a_test 194 | end. 195 | should_run_tests(1). 196 | and_output(%{ 197 | A context that isn't explicitly extended by a module 198 | should define a test within the top-level-extended module: . 199 | }). 200 | and_pass 201 | end 202 | end 203 | -------------------------------------------------------------------------------- /test/usage/07_exceptions_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ExceptionsTest < KintamaIntegrationTest 4 | 5 | def test_should_capture_exceptions_in_tests_as_failing_tests 6 | context "Given a test" do 7 | should "fail when there is an exception" do 8 | raise "aaargh" 9 | end 10 | end. 11 | should_run_tests(1). 12 | and_fail 13 | end 14 | 15 | def test_should_capture_exceptions_in_setups_as_failing_tests 16 | context "Given a test with setup that fails" do 17 | setup do 18 | raise "aargh" 19 | end 20 | 21 | should "fail even though it would otherwise pass" do 22 | assert true 23 | end 24 | end. 25 | should_run_tests(1). 26 | and_fail 27 | end 28 | 29 | def test_should_capture_exceptions_in_teardowns_as_failing_tests 30 | context "Given a test with teardown that fails" do 31 | teardown do 32 | raise "aargh" 33 | end 34 | 35 | should "fail even though it would otherwise pass" do 36 | assert true 37 | end 38 | end. 39 | should_run_tests(1). 40 | and_fail 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/usage/08_start_and_finish_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class StartAndFinishTest < KintamaIntegrationTest 4 | 5 | def setup 6 | super 7 | @order = sequence('order') 8 | end 9 | attr_reader :order 10 | 11 | def test_should_call_any_on_start_block_when_running_a_context 12 | spy = test_spy 13 | 14 | spy.expects(:in_startup).in_sequence(order) 15 | spy.expects(:in_test).in_sequence(order) 16 | 17 | context "A context with an `on_start` block" do 18 | on_start do 19 | spy.in_startup 20 | end 21 | 22 | should "have run the `on_start` block before a test" do 23 | spy.in_test 24 | end 25 | end 26 | end 27 | 28 | def test_should_only_call_on_start_block_once_when_running_a_context 29 | spy = test_spy 30 | 31 | spy.expects(:in_startup).once.in_sequence(order) 32 | spy.expects(:in_test).twice.in_sequence(order) 33 | 34 | context "A context with an `on_start` block" do 35 | on_start do 36 | spy.in_startup 37 | end 38 | 39 | should "have run the `on_start` block before a test" do 40 | spy.in_test 41 | end 42 | 43 | should "not run that block again before a second test" do 44 | spy.in_test 45 | end 46 | end 47 | end 48 | 49 | def test_should_call_on_start_block_in_nested_contexts 50 | spy = test_spy 51 | 52 | spy.expects(:outer_startup).once.in_sequence(order) 53 | spy.expects(:inner_startup).once.in_sequence(order) 54 | spy.expects(:in_test).twice.in_sequence(order) 55 | 56 | context "A context with an `on_start` block" do 57 | on_start do 58 | spy.outer_startup 59 | end 60 | 61 | context "and another `on_start` block in a nested context" do 62 | on_start do 63 | spy.inner_startup 64 | end 65 | 66 | should "have run both `on_start` blocks before running any tests" do 67 | spy.in_test 68 | end 69 | 70 | should "not run either `on_start` blocks before running subsequent tests" do 71 | spy.in_test 72 | end 73 | end 74 | end 75 | end 76 | 77 | def test_should_call_any_on_finish_block_when_running_a_context 78 | spy = test_spy 79 | 80 | spy.expects(:in_test).in_sequence(order) 81 | spy.expects(:in_finish).in_sequence(order) 82 | 83 | context "A context with an `on_finish` block" do 84 | should "not run the `on_finish` block before the test" do 85 | spy.in_test 86 | end 87 | 88 | on_finish do 89 | spy.in_finish 90 | end 91 | end 92 | end 93 | 94 | def test_should_only_call_on_finish_block_once_when_running_a_context 95 | spy = test_spy 96 | 97 | spy.expects(:in_test).twice.in_sequence(order) 98 | spy.expects(:in_finish).once.in_sequence(order) 99 | 100 | context "A context with an `on_finish` block" do 101 | should "not be run after every test" do 102 | spy.in_test 103 | end 104 | 105 | should "really not be run after every test" do 106 | spy.in_test 107 | end 108 | 109 | on_finish do 110 | spy.in_finish 111 | end 112 | end 113 | end 114 | 115 | def test_should_only_call_on_finish_block_after_all_tests 116 | spy = test_spy 117 | 118 | spy.expects(:in_test).times(3).in_sequence(order) 119 | spy.expects(:in_finish).once.in_sequence(order) 120 | 121 | context "A context with an `on_finish` block" do 122 | should "have not run the `on_finish` block before the first test" do 123 | spy.in_test 124 | end 125 | 126 | should "have not run the `on_finish` block before the second test" do 127 | spy.in_test 128 | end 129 | 130 | should "have not run the `on_finish` block before the third test" do 131 | spy.in_test 132 | end 133 | 134 | on_finish do 135 | spy.in_finish 136 | end 137 | end 138 | end 139 | 140 | def test_should_call_on_finish_block_in_nested_contexts 141 | spy = test_spy 142 | 143 | spy.expects(:in_test).twice.in_sequence(order) 144 | spy.expects(:inner_finish).once.in_sequence(order) 145 | spy.expects(:outer_finish).once.in_sequence(order) 146 | 147 | context "A context with an `on_finish` block" do 148 | context "and another `on_finish` block in a nested context" do 149 | should "not run either `on_finish` blocks before running the first test" do 150 | spy.in_test 151 | end 152 | 153 | should "not run either `on_start` blocks before running subsequent tests" do 154 | spy.in_test 155 | end 156 | 157 | on_finish do 158 | spy.inner_finish 159 | end 160 | end 161 | 162 | on_finish do 163 | spy.outer_finish 164 | end 165 | end 166 | end 167 | 168 | def test_should_not_rely_on_any_ordering_to_register_on_start_or_on_finish_blocks 169 | spy = test_spy 170 | 171 | spy.expects(:on_start).in_sequence(order) 172 | spy.expects(:in_test).in_sequence(order) 173 | spy.expects(:on_finish).in_sequence(order) 174 | 175 | context "A context with both an `on_start` and `on_finish` block" do 176 | on_finish do 177 | spy.on_finish 178 | end 179 | 180 | on_start do 181 | spy.on_start 182 | end 183 | 184 | should "allow those blocks to be defined in any order, but run them correctly" do 185 | spy.in_test 186 | end 187 | end 188 | end 189 | 190 | def test_should_be_able_to_use_rspec_like_aliases 191 | spy = test_spy 192 | 193 | spy.expects(:in_before_all).in_sequence(order) 194 | spy.expects(:in_test).in_sequence(order) 195 | spy.expects(:in_after_all).in_sequence(order) 196 | 197 | context "A context with `before_all` and `after_all` blocks" do 198 | before_all do 199 | spy.in_before_all 200 | end 201 | 202 | should "run exactly as `on_start` and `on_finish` blocks" do 203 | spy.in_test 204 | end 205 | 206 | after_all do 207 | spy.in_after_all 208 | end 209 | end 210 | end 211 | 212 | def test_should_be_able_to_run_things_once_before_any_tests_run 213 | spy = test_spy 214 | 215 | spy.expects(:before_all_tests).once.in_sequence(order) 216 | spy.expects(:in_test).twice.in_sequence(order) 217 | 218 | Kintama.reset 219 | Kintama.on_start do 220 | spy.before_all_tests 221 | end 222 | 223 | running_default_context "A context" do 224 | should "have run the `before_all_tests` block before a test" do 225 | spy.in_test 226 | end 227 | 228 | should "only run `before_all_tests` once" do 229 | spy.in_test 230 | end 231 | end 232 | end 233 | 234 | def test_should_be_able_to_run_things_after_all_tests_have_run 235 | spy = test_spy 236 | 237 | spy.expects(:in_test).twice.in_sequence(order) 238 | spy.expects(:after_all_tests).once.in_sequence(order) 239 | 240 | Kintama.reset 241 | Kintama.on_finish do 242 | spy.after_all_tests 243 | end 244 | 245 | running_default_context "A context" do 246 | should "have run the `before_all_tests` block before a test" do 247 | spy.in_test 248 | end 249 | 250 | should "only run `after_all_tests` once all tests have finished" do 251 | spy.in_test 252 | end 253 | end 254 | end 255 | 256 | private 257 | 258 | def test_spy 259 | stub('test spy', poke: nil) 260 | end 261 | end 262 | -------------------------------------------------------------------------------- /test/usage/09_expectations_and_mocking_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'kintama/mocha' 3 | 4 | class ExpectationsAndMockingTest < KintamaIntegrationTest 5 | 6 | def setup 7 | super 8 | # This is normally called automatically when requiring 'kintama/mocha' 9 | # but in these tests we totally reset Kintama, and so we need to 10 | # ensure that the mocha integration is loaded properly. 11 | Kintama::Mocha.setup 12 | end 13 | 14 | def test_should_allow_setting_of_expectations_in_tests 15 | context "Given an expectation" do 16 | setup do 17 | @thing = stub('thing') 18 | end 19 | 20 | expect "blah to be called" do 21 | @thing.expects(:blah) 22 | @thing.blah 23 | end 24 | end. 25 | should_output(%{ 26 | Given an expectation 27 | expect blah to be called 28 | }). 29 | and_pass 30 | end 31 | 32 | def test_should_report_failed_expectations_as_failures 33 | context "Given an expectation" do 34 | setup do 35 | @thing = stub('thing') 36 | end 37 | 38 | expect "blah to be called" do 39 | @thing.expects(:blah) 40 | end 41 | end. 42 | should_fail. 43 | with_failure(%{ 44 | unsatisfied expectations: 45 | - expected exactly once, invoked never: #.blah 46 | }) 47 | end 48 | 49 | def test_should_set_expectations_before_action_is_called 50 | context "Given an action" do 51 | setup do 52 | @thing = stub('thing') 53 | end 54 | 55 | action do 56 | @thing.go 57 | end 58 | 59 | expect "go to be called on thing" do 60 | @thing.expects(:go) 61 | end 62 | end. 63 | should_run_tests(1). 64 | and_pass 65 | end 66 | 67 | def test_should_not_set_expectations_for_normal_tests_defined_near_the_expect 68 | context "Given an expectation" do 69 | setup do 70 | @thing = [1,2,3] 71 | end 72 | 73 | expect "blah to be called" do 74 | @thing.expects(:join) 75 | @thing.join 76 | end 77 | 78 | it "should retain original behaviour in other tests" do 79 | assert_equal "123", @thing.join 80 | end 81 | end. 82 | should_run_tests(2). 83 | and_pass 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /test/usage/10_let_and_subject_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class LetAndSubjectTest < KintamaIntegrationTest 4 | 5 | def test_let_should_return_value_of_block_when_called_in_tests 6 | context "Defining a `let` attribute with a given name in a context" do 7 | let(:thing) do 8 | "a thing" 9 | end 10 | 11 | should "allow that attribute to be called as a method within tests" do 12 | assert_equal "a thing", thing 13 | end 14 | end. 15 | should_run_tests(1). 16 | and_pass 17 | end 18 | 19 | def test_let_should_return_the_same_instance_within_a_test 20 | context "Defining a `let` attribute in a context" do 21 | let(:thing) do 22 | Object.new 23 | end 24 | 25 | should "memoize the returned object within a test" do 26 | instance_a = thing() 27 | instance_b = thing() 28 | assert_equal instance_a.object_id, instance_b.object_id 29 | end 30 | end. 31 | should_run_tests(1). 32 | and_pass 33 | end 34 | 35 | def test_let_should_return_the_same_instance_within_a_test_and_its_setup 36 | context "Defining a `let` attribute in a context" do 37 | let(:thing) do 38 | Object.new 39 | end 40 | 41 | setup do 42 | $object_id = thing.object_id 43 | end 44 | 45 | should "memoize the returned object between setup and test" do 46 | assert $object_id && $object_id == thing.object_id 47 | end 48 | end. 49 | should_run_tests(1). 50 | and_pass 51 | end 52 | 53 | def test_let_should_return_different_instances_in_different_tests 54 | context "Defining a `let` attribute in a context" do 55 | let(:thing) do 56 | Object.new 57 | end 58 | 59 | should "return a one instance in one test" do 60 | $object_id = thing.object_id 61 | end 62 | 63 | should "return some other instance in a different test" do 64 | assert $object_id && $object_id != thing.object_id 65 | end 66 | end. 67 | should_run_tests(2). 68 | and_pass 69 | end 70 | 71 | def test_let_methods_should_be_callable_from_other_let_methods 72 | context "Defining a `let` attribute in a context" do 73 | let(:alpha) do 74 | 123 75 | end 76 | 77 | let(:beta) do 78 | alpha == 123 79 | end 80 | 81 | should "allow one `let` attribute to be referenced from another one" do 82 | assert beta 83 | end 84 | end. 85 | should_run_tests(1). 86 | and_pass 87 | end 88 | 89 | def test_subject_should_work_just_like_lets 90 | $object_id_in_test_one = nil 91 | $object_id_in_test_two = nil 92 | 93 | context "Defining a `subject` attribute in a context" do 94 | subject do 95 | Object.new 96 | end 97 | 98 | should "return one instance in one test" do 99 | $object_id_in_test_one = subject.object_id 100 | if $object_id_in_test_two # this test might run first 101 | assert_not_equal $object_id_in_test_one, $object_id_in_test_two, "object ids should be different between tests" 102 | end 103 | end 104 | 105 | should "return a different instance in a different test" do 106 | $object_id_in_test_two = subject.object_id 107 | if $object_id_in_test_one # or this test might run first 108 | assert_not_equal $object_id_in_test_one, $object_id_in_test_two, "object ids should be different between tests" 109 | end 110 | end 111 | end. 112 | should_run_tests(2). 113 | and_pass 114 | end 115 | 116 | def test_should_use_a_single_instance_of_the_subject_within_a_test 117 | context "Given a context with a subject" do 118 | subject do 119 | Array.new 120 | end 121 | 122 | should "allow me to poke around with subject like it was a variable" do 123 | subject << 1 124 | assert_equal [1], subject 125 | end 126 | 127 | should "now be empty again because it's a new instance" do 128 | assert subject.empty? 129 | end 130 | end. 131 | should_run_tests(2). 132 | and_pass 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /test/usage/11_matcher_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class MatcherTest < KintamaIntegrationTest 4 | 5 | # As well as defining tests using blocks (see Basic Usage), 6 | # the `should` method can define tests by accepting instances 7 | # of "matcher" classes. 8 | def test_should_allow_use_of_matchers_within_contexts 9 | context "The number 123" do 10 | subject do 11 | 123 12 | end 13 | 14 | should EqualMatcher.new(123) 15 | end. 16 | should_run_tests(1). 17 | and_pass 18 | end 19 | 20 | # A matcher is just an object that responds to four methods: 21 | # 22 | # * `matches?(provided_value)`, which will be called with the `subject` object 23 | # as the argument. This is where you can compare aspects of the subject 24 | # with some expected value or criteria. It should return true or false. 25 | # 26 | # * `description`, which should return a String to form part of the test name 27 | # 28 | # * `failure_message`, which returns a String describing what happened if 29 | # `matches?` returned false 30 | # 31 | # * `negative_failure_message`, which returns a String describing what 32 | # happened if the matcher was being used in a negated form (see below) 33 | # 34 | # This is an example Matcher object which can check the equality of the 35 | # subject against a given value. It's used above, and in the tests below. 36 | class EqualMatcher 37 | def initialize(expected) 38 | @expected = expected 39 | end 40 | 41 | def matches?(provided_value) 42 | @actual = provided_value 43 | @actual == @expected 44 | end 45 | 46 | def description 47 | "be equal to #{@expected.inspect}" 48 | end 49 | 50 | def failure_message 51 | "Expected #{@expected}, but got #{@actual}" 52 | end 53 | 54 | def negative_failure_message 55 | "Didn't expect #{@expected}, but got it anyway" 56 | end 57 | end 58 | 59 | def test_should_use_the_description_of_matchers_to_generate_test_names 60 | context "The number 123" do 61 | subject do 62 | 123 63 | end 64 | 65 | should EqualMatcher.new(123) 66 | end. 67 | should_output(%{ 68 | The number 123 69 | should be equal to 123: . 70 | }). 71 | and_pass 72 | end 73 | 74 | def test_should_use_the_failure_message_of_matchers_to_generate_failure_messages 75 | context "The number 456" do 76 | subject do 77 | 456 78 | end 79 | 80 | should EqualMatcher.new(123) 81 | end. 82 | should_fail. 83 | with_failure("Expected 123, but got 456") 84 | end 85 | 86 | def test_should_allow_negation_of_matchers 87 | context "The number 123" do 88 | subject do 89 | 123 90 | end 91 | 92 | should_not EqualMatcher.new(456) 93 | end. 94 | should_run_tests(1). 95 | and_pass 96 | end 97 | 98 | def test_should_generate_corresponding_test_names_for_negated_matchers 99 | context "The number 123" do 100 | subject do 101 | 123 102 | end 103 | 104 | should_not EqualMatcher.new(456) 105 | end. 106 | should_output(%{ 107 | The number 123 108 | should not be equal to 456: . 109 | }). 110 | and_pass 111 | end 112 | 113 | def test_should_use_the_negated_failure_message_of_negated_matchers_to_generate_failure_descriptions 114 | context "The number 123" do 115 | subject do 116 | 123 117 | end 118 | 119 | should_not EqualMatcher.new(123) 120 | end. 121 | should_fail. 122 | with_failure("Didn't expect 123, but got it anyway") 123 | end 124 | 125 | # You can also define methods to extend kintama for more readable 126 | # tests; instead of `should EqualMatcher.new(value)` you could write 127 | # `should be_equal_to(value)` 128 | module MethodsWhichReturnMatcherInstances 129 | def be_equal_to(expected) 130 | EqualMatcher.new(expected) 131 | end 132 | end 133 | 134 | def test_should_allow_definition_of_matchers_in_contexts 135 | Kintama.extend(MethodsWhichReturnMatcherInstances) 136 | 137 | context "The number 123" do 138 | subject do 139 | 123 140 | end 141 | 142 | should be_equal_to(123) 143 | should_not be_equal_to(456) 144 | end. 145 | should_run_tests(2). 146 | and_pass 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /test/usage/12_action_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ActionTest < KintamaIntegrationTest 4 | 5 | def test_should_run_action_between_setup_and_test 6 | context "Given a context with an `action`" do 7 | setup do 8 | @thing = 123 9 | end 10 | 11 | action do 12 | @thing += 1 13 | end 14 | 15 | it "should have called the action between the `setup` and the test" do 16 | assert @thing == 124 17 | end 18 | end. 19 | should_output(%{ 20 | Given a context with an `action` 21 | it should have called the action between the `setup` and the test: . 22 | }). 23 | and_pass 24 | end 25 | 26 | def test_should_run_action_after_all_setups 27 | context "Given an action" do 28 | setup do 29 | @thing = 123 30 | end 31 | 32 | setup do 33 | @thing *= 2 34 | end 35 | 36 | action do 37 | @thing += 1 38 | end 39 | 40 | it "should have called the action after all setup blocks" do 41 | assert @thing == 247 42 | end 43 | end. 44 | should_output(%{ 45 | Given an action 46 | it should have called the action after all setup blocks 47 | }). 48 | and_pass 49 | end 50 | 51 | def test_should_run_action_after_setups_in_nested_contexts 52 | context "Given an action" do 53 | setup do 54 | @thing = 123 55 | end 56 | 57 | action do 58 | @thing += 1 59 | end 60 | 61 | it "should have called the action after the first setup for outer tests" do 62 | assert @thing == 124 63 | end 64 | 65 | context "and a setup in a nested context" do 66 | setup do 67 | @thing *= 2 68 | end 69 | it "should have called the action after both outer and inner setups" do 70 | assert @thing == 247 71 | end 72 | end 73 | end. 74 | should_output(%{ 75 | Given an action 76 | it should have called the action after the first setup for outer tests: . 77 | and a setup in a nested context 78 | it should have called the action after both outer and inner setups: . 79 | }). 80 | and_pass 81 | end 82 | 83 | def test_should_allow_subcontexts_to_change_parameters_used_in_action 84 | context "A doubling action" do 85 | action do 86 | @result = @parameter * 2 87 | end 88 | 89 | context "when the parameter is 2" do 90 | setup do 91 | @parameter = 2 92 | end 93 | 94 | should "result in 4" do 95 | assert_equal 4, @result 96 | end 97 | end 98 | 99 | context "when the parameter is 3" do 100 | setup do 101 | @parameter = 3 102 | end 103 | 104 | should "result in 6" do 105 | assert_equal 6, @result 106 | end 107 | end 108 | end. 109 | should_output(%{ 110 | A doubling action 111 | when the parameter is 2 112 | should result in 4: . 113 | when the parameter is 3 114 | should result in 6: . 115 | }). 116 | and_pass 117 | end 118 | end 119 | --------------------------------------------------------------------------------