olimorris / neotest-rspec

🧪 Neotest adapter for RSpec. Works in Docker containers too
MIT License
90 stars 26 forks source link

Including tests from RSpec shared_examples #47

Closed stevenhansel closed 1 year ago

stevenhansel commented 1 year ago

In my rspec, I use a lot of include_examples to check the structure of the JSON response against a schema.

In my spec file, I usually include a line like this:

include_examples 'http_response', :ok, 'users/collection

My current RSpec setup is that I create a shared example like here below:

RSpec.shared_examples 'http_response' do |status_code, schema|     ■  Missing frozen string literal comment.
   it 'should have the correct status code and response body' do
     expect(response).to have_http_status(status_code)
     expect(response).to match_response_schema(schema)
   end
 end

And then in the second expect, I create a rspec matcher to automatically search the schema in a specific folder where I store all the schemas as a json file, and then I use validate that schema with the response.body

RSpec::Matchers.define :match_response_schema do |schema|
   match do |response|
     if schema.instance_of?(String)
       schema_directory = "#{Dir.pwd}/spec/schemas"
       schema_path = "#{schema_directory}/#{schema}.json"
       JSON::Validator.validate!(schema_path, response.body, strict: true)
     else
       JSON::Validator.validate!(schema, JSON.parse(response.body), strict: true)
     end
   end
 end

I see that this probably will be related to the discover_positions method from Neotest

function NeotestAdapter.discover_positions(path)
  local query = [[
    ((call
      method: (identifier) @func_name (#match? @func_name "^(describe|context|feature)$")
      arguments: (argument_list (_) @namespace.name)
    )) @namespace.definition

    ((call
      method: (identifier) @namespace.name (#match? @namespace.name "^(describe|context|feature)$")
      .
      block: (_)
    )) @namespace.definition

    ((call
      method: (identifier) @func_name (#eq? @func_name "it")
      block: (block (_) @test.name)
    )) @test.definition

    ((call
      method: (identifier) @func_name (#match? @func_name "^(it|scenario|it_behaves_like)$")
      arguments: (argument_list (_) @test.name)
    )) @test.definition
  ]]

I personally haven't dig too much on how it finds and executes the test, but I really need this feature because in every spec file I have an include_examples. I'll try to see also if I can help, thanks!

olimorris commented 1 year ago

Yep you're right. Treesitter can't extract the data that Neotest needs in order to run your tests:

Screen Shot 2023-05-28 at 10 54 15@2x

I used the online Treesitter playground to write the queries that currently work with the adapter.

I think your use case is too unique to be included in the adapter but i'd be open to a PR which allows users to pass in custom Treesitter queries for the adapter to process as part of the NeotestAdapter.discover_positions method.