- RSpec Essentials
- Mani Tadayon
- 676字
- 2021-07-09 19:33:38
Using let and context
This scenario is very common, and inevitably will make the test harder to read, increasing the potential for misunderstanding the intent, which in turn will increase the likelihood of mistakes being made when the test or related code is changed. Before we see how to improve the test, we will learn about the let
helper:
describe AddressValidator do let(:address) { {street: "123 Any Street", city: "Anytown"} } it "valid? returns false for incomplete address" do expect(AddressValidator.valid?(address)).to eq(false) end it "missing_parts returns an array of missing required parts" do expect( AddressValidator.missing_parts(address) ).to eq([:region, :postal_code, :country]) end context "invalid characters in value" do let(:address){ {street: "123 Any Street", city: "Any$town%"} } it "invalid_parts returns keys with invalid values" do expect( AddressValidator.invalid_parts(address) ).to eq([:city]) end end end
The syntax is simple. The argument for the let
helper is the name of the variable to be created, which you can reference within the same context as you would a local variable or function. In this case the argument is :address
. The let
helper also requires a block, that is is evaluated dynamically at runtime to provide the value for the object. In this case, we just supply a Hash for the value of address
.
The secret to let
is lambdas, or anonymous functions, which are evaluated at the moment they are called. Our first implementation doesn't show the power of lambdas. Using lambdas is what makes let
so flexible and effective. Instead of a single, static let
definition, we can create separate definitions for street
and city
, and reference them in address
, allowing us to change inpidual parts of address
as needed:
describe AddressValidator do let(:address) { { street: street, city: city } } let(:street) { "123 Any Street" } let(:city) { "Anytown" } it "valid? returns false for incomplete address" do expect(AddressValidator.valid?(address)).to eq(false) end it "missing_parts returns an array of missing required parts" do expect( AddressValidator.missing_parts(address) ).to eq([:region, :postal_code, :country]) end context "invalid characters in value" do let(:city) { "Any$town%" } it "invalid_parts returns keys with invalid values" do expect( AddressValidator.invalid_parts(address) ).to eq([:city]) end end end
Now we have a test case that clearly shows the differences in address
, making the intent of the test case crystal clear. Using this pattern, we can add more cases with ease. We can change one or more nested values (for example city
) or redefine address
entirely:
describe AddressValidator do # notice that 'address' is defined as a Hash here let(:address) { { street: street, city: city } } let(:street) { "123 Any Street" } let(:city) { "Anytown" } it "valid? returns false for incomplete address" do expect(AddressValidator.valid?(address)).to eq(false) end context "address contains invalid characters" do # here we've redefined 'address' to be a String let(:address) { "$123% A^ St., Anytown, CA, USA 12345" } it "valid? returns false for incomplete address" do expect(AddressValidator.valid?(address)).to eq(false) end end context "address is a String" do let(:address) { "123 Any St., Anytown" } it "valid? returns false for incomplete address" do expect(AddressValidator.valid?(address)).to eq(false) end end context "complete address" do # we define 'address' as a Hash, but with all values let(:address) do { street: "123 Any Street", city: "Anytown", region: "Anyplace", country: "Anyland", postal_code: "123456" } end it "valid? returns true" do expect(AddressValidator.valid?(address)).to eq(true) end context "address is a String" do let(:address) { "123 Any St., Anytown, CA, USA, 12345" } it "valid? returns true" do expect(AddressValidator.valid?(address)).to eq(true) end end end end
We've just seen how let
is a simple but effective tool for organizing tests, making them easy to understand and to maintain. We've also started using context
in order to organize our tests. In fact, context
is just an alias for describe
. Often, the outermost grouping of RSpec examples is defined with describe
and the inner groups are grouped using context
, but both are different names for the exact same function. In the cases we have seen, context
gives us a local scope where we can define different versions of our test inputs with let
.
Now, let's move on to matchers, which give us a flexible way of making assertions.
- Web前端開發技術:HTML、CSS、JavaScript(第3版)
- C語言程序設計習題解析與上機指導(第4版)
- Java 開發從入門到精通(第2版)
- Android開發精要
- Java程序員面試算法寶典
- Building Mobile Applications Using Kendo UI Mobile and ASP.NET Web API
- PHP 編程從入門到實踐
- 小程序開發原理與實戰
- C語言程序設計教程
- Extending Unity with Editor Scripting
- STM8實戰
- Mastering Machine Learning with scikit-learn
- 歐姆龍PLC編程指令與梯形圖快速入門
- 深度學習的數學:使用Python語言
- TensorFlow 2.0深度學習應用實踐