Test Assertions
When writing tests you often need to check that a value meets certain criterias. The expect()
function gives you access to an array of assertions that lets you test against different conditions. This page describes assertions, how to use them and all possible assertions in Qi.
expect()
The expect
function is used every time you want to test a value. You will rarely call expect
by itself. Instead, you will use expect
along with a "matcher" function to assert something about a value. Let's say you have a method name_of_app()
which is supposed to return the string 'qi'
. Here's how you would test that:
describe('Name of app test', @{
it('should be qi', @{
expect(name_of_app()).to_be('qi')
})
})
In this case, to_be
is the matcher function. There are a lot of different matcher functions, documented below, to help you test different things.
The argument to expect
should be the value that your code produces, and any argument to the matcher should be the correct value. If you mix them up, your tests will still work, but the reporting messages show after running tests will look strange.
Modifiers
Nyssa offers modifiers that allow you to change the behavior of a test assertion and/or what they mean all-together.
- . not()
-
If you know how to test something,
.not()
lets you test its opposite. For example, this code tests that the name of the application isnot
'qi'
.describe('Name of app test', @{ it('should be qi', @{ expect(name_of_app()).not().to_be('qi') }) })
Matchers
NOTE:
Matchers can be nested. For example,
expect(10.5).to_be_number().to_be_less_than(20)
- .to_be(value)
-
Use
.to_be
to compare primitive values or to check referential identity of object instances. For example, this code will validate some properties of thecan
object:var can = { name: 'pamplemousse', ounces: 12, } describe('the can', @{ it('has 12 ounces', @{ expect(can.ounces).to_be(12) }) it('has a sophisticated name', @{ expect(can.name).to_be('pamplemousse') }) })
- .to_be_nil()
-
.to_be_nil()
is the same as.to_be(nil)
but the error messages are a bit nicer. So use.to_be_nil()
when you want to check that something is nil.def bloop() { return nil } it('should return nil', @{ expect(bloop()).to_be_nil() })
- .to_be_defined()
-
Use
.to_be_defined
to check that a variable is notnil
. For example, if you want to check that a functionfetch_new_flavor_idea()
returns something, you can write:expect(fetch_new_flavor_idea()).to_be_defined()
You could also write
expect(fetch_new_flavor_idea()).not().to_be_nil()
as they are identical, but it's better practice to use the direct method. - .to_be_truthy()
-
Use
.to_be_truthy
when you don't care what a value is and you want to ensure a value is true in a boolean context. For example, let's say you have some application code that looks like:drink_some_lacroix() if thirsty() { drink_more_lacroix() }
You may not care what
get_errors
returns, specifically - it might returntrue
,[1]
, or anything that's true in Blade, and your code would still work. So if you want to test you are thirsty before drinking some La Croix, you could write:it('should be thirsty before drinking La Croix', @{ drink_some_lacroix() expect(thirsty()).to_be_truthy() })
- .to_be_falsy()
-
Use
.to_be_falsy
when you don't care what a value is and you want to ensure a value is false in a boolean context. For example, let's say you have some application code that looks like:drink_some_lacroix() if !get_errors() { drink_more_lacroix() }
You may not care what
get_errors
returns, specifically - it might returnfalse
,nil
, or-1
, and your code would still work. So if you want to test there are no errors after drinking some La Croix, you could write:it('does not lead to errors when drinking La Croix', @{ drink_some_lacroix() expect(get_errors()).to_be_falsy() })
- .to_be_greater_than(number)
-
Use
.to_be_greater_than
to comparereceived > expected
for number orreceived.length() > expected
for string. For example, test thatounces_per_can()
returns a value of more than 10 ounces:it('is more than 10 ounces per can', @{ expect(ounces_per_can()).to_be_greater_than(10) })
- .to_be_greater_than_or_equal(number)
-
Use
.to_be_greater_than_or_equal
to comparereceived >= expected
for number orreceived.length() >= expected
for string. For example, test thatounces_per_can()
returns a value of more than or equal to 10 ounces:it('is more than or equal to 10 ounces per can', @{ expect(ounces_per_can()).to_be_greater_than_or_equal(10) })
- .to_be_less_than(number)
-
Use
.to_be_less_than
to comparereceived < expected
for number orreceived.length() < expected
for string. For example, test thatounces_per_can()
returns a value of less than 10 ounces:it('is less than 10 ounces per can', @{ expect(ounces_per_can()).to_be_less_than(10) })
- .to_be_less_than_or_equal(number)
-
Use
.to_be_less_than_or_equal
to comparereceived <= expected
for number orreceived.length() <= expected
for string. For example, test thatounces_per_can()
returns a value of less than or equal to 10 ounces:it('is less than or equal to 10 ounces per can', @{ expect(ounces_per_can()).to_be_less_than_or_equal(10) })
- .to_match(value)
-
Use
.to_match
to check that a string matches a regular expression.For example, you might not know what exactly
essay_on_the_best_flavor()
returns, but you know it's a really long string, and the substring grapefruit should be in there somewhere. You can test this with:describe('an essay on the best flavor', @{ it('mentions grapefruit', @{ expect(essay_on_the_best_flavor()).to_match('/grapefruit/i') }) })
This matcher also accepts a string, which it will try to match:
describe('grapefruits', @{ it('should be a grape', @{ expect('grapefruits').to_match('grape') }) })
- .to_contain(item)
-
Use
.to_contain
when you want to check that an item is in an list or dictionary or whether a string is a substring of another string.For example, if
get_all_flavors()
returns an list of flavors and you want to be sure that lime is in there, you can write:it('should contain lime', @{ expect(get_all_flavors()).to_contain('lime') })
- .to_throw(error?)
-
Use
.to_throw
to test that a function throws when it is called. For example, if we want to test thatdrink_flavor('octopus')
throws, because octopus flavor is too disgusting to drink, we could write:it('throws on octopus', @{ expect(@{ drink_flavor('octopus') }).to_throw() })
NOTE:
You must wrap the code in a function, otherwise the error will not be caught and the assertion will fail.
You can provide an optional argument to test that a specific error is thrown:
- string: error message includes the substring
- regular expression: error message matches the pattern
- error object: error message is equal to the message property of the object
- error class: error object is instance of class
For example, let's say
drink_flavor()
looks like this:def drink_flavor(flavor) { if flavor == 'octopus' { raise DisgustingFlavorError('yuck, octopus flavor') } # Do some other stuff }
We could test the error thrown in several ways:
it('throws on octopus', @{ def drink_octopus() { drink_flavor('octopus') } # Test that the error message says "yuck" somewhere: these are equivalent expect(drink_octopus).to_throw('/yuck/') expect(drink_octopus).to_throw('yuck') # Test the exact error message expect(drink_octopus).to_throw('/^yuck, octopus flavor$/') expect(drink_octopus).to_throw(Exception('yuck, octopus flavor')) # Test that we get a DisgustingFlavorError expect(drink_octopus).to_throw(DisgustingFlavorError) })
- .to_have_length(number)
-
Use
.to_have_length
to check that an object has a .length property and it is set to a certain numeric value. For example:expect([1, 2, 3]).to_have_length(3) expect('abc').to_have_length(3) expect('').not().to_have_length(5)
- .to_be_instance_of(class)
-
Use
.to_be_instance_of(class)
to check that an object is an instance of a class. This matcher usesinstance_of
underneath.class A {} expect(A()).to_be_instance_of(A) expect(A()).to_be_instance_of(Exception) # fails
- .to_be_function(value?)
-
Use
.to_be_function
when you want to check if a value is a function or a closure. For example, ifdo_something()
is a function looking like this:def do_something(id) { if id == 1 return @{ do_another_thing() } else return @{ do_something_else() } }
We can test that
do_something()
correctly returns a function.expect(do_something(1)).to_be_function()
- .to_have_property(name, value?)
-
Use
.to_have_property
to check if an object has a given property. You can provide an optional value argument to compare the received property value against an expected value.class A { var name = 'something' } expect(A()).to_have_property('name') expect(A()).to_have_property('name', 'something')
It's important to note that when value is given, value must not be
nil
. - .to_have_method(name)
-
Use the
.to_have_method
to check if an object is an instance of a class having a particular method. For example, let's say you have a classA
andB
defined as follows:class A { testing() {} } class B { testing() {} }
and you have a function
return_class()
that could return an instance of any ofA
orB
, you can test the output of that method like,expect(return_class()).to_have_method('testing')
- .to_have_decorator(name)
-
Use the
.to_have_decorator
to check if an object is an instance of a class having a particular decorator. For example, let's say you have a classA
andB
defined as follows:class A { @testing() {} } class B { @testing() {} }
and you have a function
return_class()
that could return an instance of any ofA
orB
, you can test the output of that method like,expect(return_class()).to_have_decorator('testing')
- .to_be_boolean()
-
Use
.to_be_boolean
to check fortrue
orfalse
values. For example, test thatuser_is_admin()
returns a value oftrue
orfalse
:it('should be true or false', @{ expect(user_is_admin()).to_be_boolean() })
- .to_be_number()
-
Use
.to_be_number
to check that a value is a number without requiring any specific number. For example, test thatnumber_of_cans()
returns a valid number:it('should be a number', @{ expect(number_of_cans()).to_be_number() })
- .to_be_string()
-
Use
.to_be_string
to check that a value is a string without requiring any specific content. For example, test thatname_of_king()
returns a valid string:it('should be a string', @{ expect(name_of_king()).to_be_string() })
- .to_be_list()
-
Use
.to_be_list
to check that a value is a list without requiring any specific content. For example, test thatfruits()
returns a valid list:it('should be a string', @{ expect(fruits()).to_be_list() })
- .to_be_dict()
-
Use
.to_be_dict
to check that a value is a dictionary without requiring any specific content. For example, test that{age: 10}
returns a valid dictionary:it('should be a dictionary', @{ expect({age: 10}).to_be_dict() })
- .to_be_class()
-
Use
.to_be_class
to check that a value is a class and not an instance. For example, test thatException
is actually a class:it('should be a list', @{ expect(Exception).to_be_class() })
- .to_be_iterable()
-
Use
.to_be_iterable
to check that a value is an iterable whether its of basic types (e.g. String, List etc.) or an iterable class. For example, suppose we have a classSet
defined ass follows:class Set { @iter() {} @itern() {} }
The following test will show that it's as much an iterable as a list or dictionary can be.
it('should be enumerable', @{ expect([]).to_be_iterable() expect({}).to_be_iterable() expect(Set()).to_be_iterable() })
- .to_be_file()
-
Use
.to_be_file
to check that a value is a file object. For example, you can test that an handlefh
returned by the functionget_config()
is actually a file like this:var fh = get_config() expect(fh).to_be_file()
- .to_be_bytes()
-
Use
.to_be_bytes
to check that a value is an array of bytes. For example,expect(bytes(0)).to_be_bytes()