Module: NRSER::RSpex::ExampleGroup

Defined in:
lib/nrser/rspex/example_group/overrides.rb,
lib/nrser/rspex/example_group.rb,
lib/nrser/rspex/example_group/describe_x.rb,
lib/nrser/rspex/example_group/describe_case.rb,
lib/nrser/rspex/example_group/describe_when.rb,
lib/nrser/rspex/example_group/describe_class.rb,
lib/nrser/rspex/example_group/describe_group.rb,
lib/nrser/rspex/example_group/describe_setup.rb,
lib/nrser/rspex/example_group/describe_method.rb,
lib/nrser/rspex/example_group/describe_module.rb,
lib/nrser/rspex/example_group/describe_message.rb,
lib/nrser/rspex/example_group/describe_section.rb,
lib/nrser/rspex/example_group/describe_sent_to.rb,
lib/nrser/rspex/example_group/describe_instance.rb,
lib/nrser/rspex/example_group/describe_attribute.rb,
lib/nrser/rspex/example_group/describe_spec_file.rb,
lib/nrser/rspex/example_group/describe_called_with.rb,
lib/nrser/rspex/example_group/describe_response_to.rb,
lib/nrser/rspex/example_group/describe_source_file.rb,
lib/nrser/rspex/example_group/describe_instance_method.rb

Overview

Definitions

Defined Under Namespace

Modules: Overrides

Instance Method Summary collapse

Instance Method Details

#describe_attribute(symbol, **metadata, &body) ⇒ void Also known as: describe_attr, ATTRIBUTE

This method returns an undefined value.

Describe an attribute of the parent subject.



10
11
12
13
14
15
16
17
18
19
# File 'lib/nrser/rspex/example_group/describe_attribute.rb', line 10

def describe_attribute symbol, **, &body
  describe_x \
    NRSER::RSpex::Format.md_code_quote( "##{ symbol }" ),
    type: :attribute,
    metadata: ,
    subject_block: -> {
      super().public_send symbol
    },
    &body
end

#describe_called(&body) ⇒ void Also known as: called, when_called, CALLED

This method returns an undefined value.

Version of #describe_called_with for when you have no arguments.

Parameters:

  • body (#call)

    Block to execute in the context of the example group after refining the subject.



49
50
51
52
53
54
# File 'lib/nrser/rspex/example_group/describe_called_with.rb', line 49

def describe_called &body
  describe_x Args(),
    type: :called_with,
    subject_block: -> { super().call },
    &body
end

#describe_called_with(*args, &body) ⇒ void Also known as: called_with, CALLED_WITH

This method returns an undefined value.

Create a new RSpec.describe section where the subject is set by calling the parent subject with ‘args` and evaluate `block` in it.

Examples:

describe "hi sayer" do
  subject{ ->( name ) { "Hi #{ name }!" } }

  describe_called_with 'Mom' do
    it { is_expected.to eq 'Hi Mom!' }
  end
end

Parameters:

  • args (Array)

    Arguments to call ‘subject` with to produce the new subject.

  • body (#call)

    Block to execute in the context of the example group after refining the subject.



27
28
29
30
31
32
# File 'lib/nrser/rspex/example_group/describe_called_with.rb', line 27

def describe_called_with *args, &body
  describe_x Args(*args),
    type: :called_with,
    subject_block: -> { super().call *args },
    &body
end

#describe_case(*description, where: {}, **metadata, &body) ⇒ void Also known as: use_case, CASE, describe_use_case

TODO:

Document describe_use_case method.

This method returns an undefined value.



9
10
11
12
13
14
15
16
# File 'lib/nrser/rspex/example_group/describe_case.rb', line 9

def describe_case *description, where: {}, **, &body
  describe_x \
    *description,
    type: :case,
    bindings: where,
    metadata: ,
    &body
end

#describe_class(klass, *description, bind_subject: true, **metadata, &body) ⇒ void Also known as: CLASS

TODO:

Document describe_class method.

This method returns an undefined value.



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/nrser/rspex/example_group/describe_class.rb', line 10

def describe_class  klass,
                    *description,
                    bind_subject: true,
                    **,
                    &body
  subject_block = if bind_subject
    -> { klass }
  end
  
  describe_x \
    NRSER::RSpex::Format.md_code_quote( klass.name ),
    klass.source_location,
    *description,
    type: :class,
    metadata: {
      **,
      class: klass,
    },
    subject_block: subject_block,
    &body
end

#describe_group(*description, **metadata, &body) ⇒ void

This method returns an undefined value.

Describe a “group”. Doesn’t really do much. Didn’t end up getting used much. Probably not long for this world.

Parameters:

  • metadata (Hash<Symbol, Object>)

    RSpec metadata to set for the example group.

    See the ‘metadata` keyword argument to #describe_x.



20
21
22
23
24
25
26
27
# File 'lib/nrser/rspex/example_group/describe_group.rb', line 20

def describe_group *description, **, &body
  # Pass up to {#describe_x}
  describe_x \
    *description,
    type: :group,
    metadata: ,
    &body
end

#describe_instance(*constructor_args, &body) ⇒ void Also known as: INSTANCE

This method returns an undefined value.

Describe an instance of the described class by providing arguments for it’s construction.

Parameters:

  • constructor_args (Array)

    Arguments to pass to ‘.new` on #described_class to create instances.



13
14
15
16
17
18
19
20
21
22
23
# File 'lib/nrser/rspex/example_group/describe_instance.rb', line 13

def describe_instance *constructor_args, &body
  describe_x ".new", Args(*constructor_args),
    type: :instance,
    metadata: {
      constructor_args: constructor_args,
    },
    subject_block: -> {
      described_class.new *described_constructor_args
    },
    &body
end

#describe_instance_method(name, **metadata, &block) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/nrser/rspex/example_group/describe_instance_method.rb', line 5

def describe_instance_method name, **, &block
  describe(
    "##{ name }",
    type: :instance_method,
    method_name: name,
    **
  ) do
    if name.is_a? Symbol
      subject { super().method name }
    end
    
    module_exec &block
  end
end

#describe_message(*args, &body) ⇒ void

Note:

Since the block is used for the example group body, if you want to describe a message with a Message#block your need to create the message yourself and pass it as the only argument.

This method returns an undefined value.

Describe a Message. Useful when you have a message that you want to send to many receivers (see #describe_sent_to).

Parameters:

See Also:



23
24
25
26
27
28
29
30
31
32
33
# File 'lib/nrser/rspex/example_group/describe_message.rb', line 23

def describe_message *args, &body
  message = NRSER::Message.from *args
  
  describe_x \
    description,
    type: :message,
    metadata: {
      message: message,
    },
    &body
end

#describe_method(method, *description, bind_subject: nil, **metadata, &body) ⇒ void Also known as: METHOD

This method returns an undefined value.

Describe a method of the parent subject.

Parameters:



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/nrser/rspex/example_group/describe_method.rb', line 17

def describe_method method,
                    *description,
                    bind_subject: nil,
                    **,
                    &body
  case method
  when Method
    method_name = method.name
    subject_block = -> { method }
    bind_subject = true
    name_string = NRSER::RSpex::Format.md_code_quote \
      "#{ method.receiver }.#{ method.name }"
  
  when Symbol, String
    method_name = method

    # Due to legacy, we only auto-bind if the name is a symbol
    # 
    # TODO  Get rid of this
    # 
    bind_subject = method_name.is_a?( Symbol ) if bind_subject.nil?

    subject_block = if bind_subject
      -> { super().method method_name }
    end

    name_prefix = if  self.respond_to?( :metadata ) &&
                      self..key?( :constructor_args )
      '#'
    else
      '.'
    end
  
    method = if self.try( :metadata )
      getter = if self..key?( :constructor_args )
        :instance_method
      else
        :method
      end
      
      target = self.[:class] || self.[:module]
      
      if target
        begin
          target.public_send getter, method_name
        rescue
          nil
        end
      end
    end
  
    name_string = NRSER::RSpex::Format.md_code_quote \
      "#{ name_prefix }#{ method_name }"
  
  else
    raise NRSER::TypeError.new \
      "Expected Method, Symbol or String for `method_name`, found",
      method_name
    
  end # case method_arg
  
  # Create the RSpec example group context
  describe_x \
    name_string,
    NRSER::Meta::Source::Location.new( method ),
    *description,
    type: :method,
    metadata: {
      **,
      method_name: method_name,
    },
    bind_subject: bind_subject,
    subject_block: subject_block,
    &body
end

#describe_module(mod, bind_subject: true, **metadata, &body) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
# File 'lib/nrser/rspex/example_group/describe_module.rb', line 6

def describe_module mod, bind_subject: true, **, &body
  describe_x \
    mod,
    type: :module,
    metadata: {
      module: mod,
      **,
    },
    bind_subject: bind_subject,
    subject_block: -> { mod },
    &body
end

#describe_response_to(*args, &body) ⇒ void Also known as: describe_return_value

This method returns an undefined value.

Describe the response of the subject to a Message.

Pretty much a short-cut for nesting #describe_method / #describe_called_with. Meh.

Parameters:



18
19
20
21
22
23
24
25
26
27
# File 'lib/nrser/rspex/example_group/describe_response_to.rb', line 18

def describe_response_to *args, &body
  msg = NRSER::Message.from *args
  
  # Pass up to {#describe_x}
  describe_x \
    msg,
    type: :response_to,
    subject_block: -> { msg.send_to super() },
    &body
end

#describe_section(*description, **metadata, &body) ⇒ void Also known as: describe_topic, SECTION

This method returns an undefined value.

Describe a “section”. Just like RSpec.describe except it:

  1. Expects a string title.

  2. Prepends a little section squiggle Ԥ` to the title so sections are easier to pick out visually.

  3. Adds ‘type: :section` metadata.

Parameters:

  • metadata (Hash<Symbol, Object>)

    RSpec metadata to set for the example group.

    See the ‘metadata` keyword argument to #describe_x.



26
27
28
29
30
31
32
33
# File 'lib/nrser/rspex/example_group/describe_section.rb', line 26

def describe_section *description, **, &body
  # Pass up to {#describe_x}
  describe_x \
    *description,
    type: :section,
    metadata: ,
    &body
end

#describe_sent_to(receiver, publicly: true, bind_subject: true, &body) ⇒ void Also known as: sent_to

This method returns an undefined value.

For use when ‘subject` is a Message. Create a new context for the `receiver` where the subject is the result of sending that message to the receiver.

Parameters:

  • receiver (Object)

    Object that will receive the message to create the new subject.

    If it’s a Wrapper it will be unwrapped in example contexts of the new example group.

  • publicly (Boolean) (defaults to: true)

    Send message publicly via Object#public_send (default) or privately via Object.send.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/nrser/rspex/example_group/describe_sent_to.rb', line 25

def describe_sent_to  receiver,
                      publicly: true,
                      bind_subject: true,
                      &body
  mode = if publicly
    "publicly"
  else
    "privately"
  end
  
  describe_x \
    receiver,
    "(#{ mode })",
    type: :sent_to,
    bind_subject: bind_subject,
    subject_block: -> {
      super().send_to \
        unwrap( receiver, context: self ),
        publicly: publicly
    },
    &body
end

#describe_setup(*description, **metadata, &body) ⇒ void Also known as: setup, SETUP

This method returns an undefined value.

Setup describes what’s going to be done in all child examples.

It’s where you setup your ‘subject`, usually depending on `let` bindings that are provided in the children.



12
13
14
15
16
17
18
# File 'lib/nrser/rspex/example_group/describe_setup.rb', line 12

def describe_setup *description, **, &body
  describe_x \
    *description,
    type: :setup,
    metadata: ,
    &body
end

#describe_source_file(path, *description, **metadata, &body) ⇒ void

Note:

Honestly, now that modules, classes and methods described through RSpex add their source locations, this is not all that useful. But it was there from before that, which is why for the moment it’s still here.

This method returns an undefined value.

Create an example group covering a source file.

Useful for when method implementations are spread out across multiple files but you want to group examples by the source file they’re in.

Parameters:

See Also:



35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/nrser/rspex/example_group/describe_source_file.rb', line 35

def describe_source_file path, *description, **, &body
  path = path.to_pn
  
  describe_x \
    path,
    *description,
    type: :source_file,
    metadata: {
      source_file: path,
      **,
    },
    &body
end

#describe_spec_file(description: nil, spec_path:, bind_subject: true, **metadata, &body) ⇒ void Also known as: SPEC_FILE

TODO:

This is totally just a one-off right now… would need to be generalized quite a bit…

  1. Extraction of module, class, etc from metadata should be flexible

  2. Built description would need to be conditional on what metadata was found.

This method returns an undefined value.

EXPERIMENTAL

Example group helper for use at the top level of each spec file to set a bunch of stuff up and build a helpful description.

Parameters:

  • description (String) (defaults to: nil)

    A description of the spec file to add to the RSpec description.

  • spec_path (String)

    The path to the spec file (just feed it ‘__FILE__`).

    Probably possible to extract this somehow without having to provide it?



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/nrser/rspex/example_group/describe_spec_file.rb', line 51

def describe_spec_file  description: nil,
                        spec_path:,
                        bind_subject: true,
                        **,
                        &body
  
  chain = []
  
  [
    :source_file,
    :module,
    :class,
    :instance,
    :method,
    :instance_method,
    :called_with,
    :attribute,
  ].each do |type|
    if data = .delete( type )
      chain << [type, data]
    end
  end
  
  describe_x_body = if chain.empty?
    body
  else
    -> { dive_x *chain, bind_subject: bind_subject, &body }
  end
  
  describe_x \
    NRSER::RSpex.dot_rel_path( spec_path ),
    *description,
    type: :spec_file,
    metadata: ,
    &describe_x_body
  
end

#describe_when(*description, **bindings, &body) ⇒ void Also known as: context_where, _when, WHEN

This method returns an undefined value.

Define a example group with the keyword args as bindings.

Parameters:

See Also:



19
20
21
22
23
24
25
# File 'lib/nrser/rspex/example_group/describe_when.rb', line 19

def describe_when *description, **bindings, &body
  describe_x \
    *description,
    type: :when,
    bindings: bindings,
    &body
end

#describe_x(*description, type:, metadata: {}, bindings: {}, add_binding_desc: true, bind_subject: true, subject_block: nil, &body) ⇒ void Also known as: describe_x_type

This method returns an undefined value.

The core, mostly internal method that all RSpex’s description methods lead back too (or should / will when refactoring is done).

Keyword options are explicitly broken out in this method, versus the sugary ones that call it, so ‘metadata` can be set without restriction (save the `type` key, which is also it’s own keyword here). You can use this method if you want the RSpex functionality but absolutely have to set some metadata key that we use for something else.

Parameters:

  • description (Array)

    Optional list of elements that compose the custom description.

    Will be passed to Format.description to produce the string value that is in turn passed to RSpec.describe.

  • type (Symbol)

    The RSpex “type” of the example group, which is used to determine the prefix of the final description and is assigned to the ‘:type` metadata key.

  • metadata (Hash<Symbol, Object>) (defaults to: {})
    RSpec metadata][

    to add to the new example group.

    In addition to the keys RSpec will reject, we prohibit ‘:type` unless it is the same as the `type` keyword argument or `nil`.

    In either of these cases, the ‘type` keyword arg will be used for the new example group’s ‘:type` metadata value.

    [RSpec metadata]: relishapp.com/rspec/rspec-core/docs/metadata/user-defined-metadata

  • bindings (Hash<Symbol, Object>) (defaults to: {})

    Name to value pairs to bind in the new example group.

    All values will be bound at the example group and example levels - though if they are Wrapper, that wrapper will be available at the group level, while they will be automatically unwrapped at the example level (as the requisite context is available there).

  • bind_subject (Boolean) (defaults to: true)

    When ‘true` (and there is a `subject_block`) bind the `subject` inside the new example group.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/nrser/rspex/example_group/describe_x.rb', line 52

def describe_x  *description,
                type:,
                metadata: {},
                bindings: {},
                add_binding_desc: true,
                bind_subject: true,
                subject_block: nil,
                &body
  
  # Check that `metadata` doesn't have a `:type` value too... although we
  # allow it if's equal to `type` or `nil` 'cause why not I guess?
  # 
  if  .key?( :type ) &&
      [:type] != nil &&
      [:type] != type
    raise ArgumentError.new binding.erb <<-END
      `metadata:` keyword argument may not have a `:type` key that conflicts
      with the `type:` keyword argument.
      
      Received:
        `type`:
        
            <%= type.inspect %>
        
        `metadata[:type]`:
        
            <%= metadata[:type].pretty_inspect %>
      
    END
  end
  
  # Add description of the bindings, if we have any and were told to
  unless bindings.empty? || add_binding_desc == false
    bindings_desc =  NRSER::RSpex::Format.md_code_quote \
      bindings.map { |name, value|
        "#{ name } = #{ value.inspect }"
      }.join( '; ' )
    
    if description.empty?
      description = bindings_desc
    else
      description << "(" + bindings_desc + ")"
    end
  end
  
  # Call up to RSpec's `#describe` method
  describe(
    NRSER::RSpex::Format.description( *description, type: type ),
    **,
    type: type,
  ) do
    if subject_block && bind_subject
      subject &subject_block
    end
    
    # Bind bindings
    unless bindings.empty?
      bindings.each { |name, value|
        # Example-level binding
        let( name ) { unwrap value, context: self }
        
        # Example group-level binding (which may return a {Wrapper} that
        # of course can not be unwrapped at the group level)
        define_singleton_method( name ) { value }
      }
    end
    
    module_exec &body
  end # describe
  
end

#dive_x(current, *rest, **kwds, &body) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/nrser/rspex/example_group/describe_spec_file.rb', line 7

def dive_x current, *rest, **kwds, &body
  type, data = current
  
  method_name = "describe_#{ type }"
  
  block = if rest.empty?
    body
  else
    -> { dive_x *rest, &body }
  end
  
  begin
    public_send method_name, data, **kwds, &block
  rescue NoMethodError => error
    pp self.methods
    raise error
  end
end