Class: Brut::SpecSupport::RSpecSetup

Inherits:
Object
  • Object
show all
Defined in:
lib/brut/spec_support/rspec_setup.rb

Overview

Configures RSpec for Brut. This provides several bits of infrastructure only present when the app is running, as well as quality of life improvements to make testing Brut components a bit easier. Even though Brut classes are all normal classes, it's convenient to have extraneous classes set up for you.

  • Metadata is added based on the class names under test:
    • *Component -> :component
    • *Page -> :component
    • *Page -> :page
    • *Handler -> :handler
    • If you add the :page metadata automatically, :component is added as well.
    • Tests in specs/e2e or any subfolder -> :e2e
    • Note that you are free to explicitly add these tags to your test metadata. That will cause the inclusion of modules as described below
  • Modules are included to provide additional support for writing tests:
    • GeneralSupport included in all tests
    • ComponentSupport included when :component metadata is set (generally, this is for page and component tests)
    • HandlerSupport included when :handler is set.
    • included when :e2e is set, which allows use of the Ruby binding for Playwright.
  • Non end-to-end tests will be run inside a database transaction to allow all database changes to be instantly undone. This does mean that any tests that tests database transaction behavior will not work as expected.
  • In component tests (which generally includes page tests), a {Brut::FrontEnd::RequestContext is created for you and placed into the thread local storage. This allows any component that is injected with data from the RequestContext to access it as it would normally. You can also seed this with data that a component may need.
  • Handles all infrastructure for end-to-end tests:
    • starting a test server using E2ETestServer
    • launching Chromium via Playwright
  • If using Sidekiq:
    • Jobs are cleared before each test
    • For end-to-end tests, Redis is flushed and actual Sidekiq is used instead of testing mode

You can set certain metadata to change behavior:

  • e2e_timeout - number of milliseconds to wait until an end-to-end test gives up on a selector being found. The default is 5,000 (5 seconds). Use this only if there's no other way to keep your test from needing more than 5 seconds.

Examples:

RSpec.configure do |config|
  rspec_setup = Brut::SpecSupport::RSpecSetup.new(rspec_config: config)
  rspec_setup.setup!

  # rest of the RSpec configuration
end

Defined Under Namespace

Classes: OptionalSidekiqSupport

Instance Method Summary collapse

Constructor Details

#initialize(rspec_config:, monkey_patch_summary_notification: true) ⇒ RSpecSetup

Create the setup with the given RSpec configuration.

RSpec's Notifications::SumaryNotification will be monkey-patched to show bin/test in the summary instead of ./rspec, as that is how you would re-run a test. But, it's still monkey-patching.

Parameters:

  • rspec_config (RSpec::Core::Configuration)

    yielded from RSpec.configure

  • monkey_patch_summary_notification (boolean) (defaults to: true)

    if true (the default),



50
51
52
53
54
55
56
57
# File 'lib/brut/spec_support/rspec_setup.rb', line 50

def initialize(rspec_config:,
               monkey_patch_summary_notification: true)

  @config                            = rspec_config
  @monkey_patch_summary_notification = monkey_patch_summary_notification

  SemanticLogger.default_level = ENV.fetch("LOGGER_LEVEL_FOR_TESTS","warn")
end

Instance Method Details

#setup!(inside_db_transaction: ->() {}) ⇒ Object

Sets up RSpec with variouis configurations needed by Brut to run your tests.

Parameters:

  • inside_db_transaction (Proc) (defaults to: ->() {})

    if given, this is run inside the DB transaction before your example is run. This is useful if you need to set up some reference data for all tests.



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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/brut/spec_support/rspec_setup.rb', line 62

def setup!(inside_db_transaction: ->() {})

  Brut::FactoryBot.new.setup!
  optional_sidekiq_support = OptionalSidekiqSupport.new

  @config. do ||
    if [:described_class].to_s =~ /[a-z0-9]Component$/ ||
        [:described_class].to_s =~ /[a-z0-9]Page$/ ||
        [:page] == true
      [:component] = true
    end
    if [:described_class].to_s =~ /[a-z0-9]Page$/ ||
        [:page] == true
      [:page] = true
    end
    if [:described_class].to_s =~ /[a-z0-9]Handler$/
      [:handler] = true
    end

    relative_path = Pathname([:absolute_file_path]).relative_path_from(Brut.container.app_specs_dir)

    top_level_directory = relative_path.each_filename.to_a[0].to_s
    if top_level_directory == "e2e"
      [:e2e] = true
    end
  end
  @config.include Brut::SpecSupport::GeneralSupport
  @config.include Brut::SpecSupport::ComponentSupport, component: true
  @config.include Brut::SpecSupport::HandlerSupport, handler: true
  @config.include Brut::SpecSupport::E2eSupport, e2e: true
  @config.include Playwright::Test::Matchers, e2e: true

  @config.around do |example|

    needs_request_context = example.[:component] ||
                            example.[:handler]   ||
                            example.[:page]

    if needs_request_context
      session = {
        "session_id" => "test-session-id",
        "csrf" => "test-csrf-token"
      }
      env = {
        "rack.session" => session
      }
      app_session = Brut.container.session_class.new(rack_session: session)
      request_context = Brut::FrontEnd::RequestContext.new(
        env: env,
        session: app_session,
        flash: empty_flash,
        body: nil,
        xhr: false,
        host: URI("https://example.com")
      )
      Thread.current.thread_variable_set(:request_context, request_context)
      example.example_group.let(:request_context) { request_context }
    end
    if example.[:component]
      example.example_group.let(:component_name) { described_class.component_name }
    end
    if example.[:page]
      example.example_group.let(:page_name) { described_class.page_name }
    end

    if example.[:e2e]
      e2e_timeout = (ENV["E2E_TIMEOUT_MS"] || example.[:e2e_timeout] || 5_000).to_i
      optional_sidekiq_support.disable_sidekiq_testing do
        Brut::SpecSupport::E2ETestServer.instance.start
        Playwright.create(playwright_cli_executable_path: "./node_modules/.bin/playwright") do |playwright|
          launch_args = {
            headless: true,
          }
          if ENV["E2E_SLOW_MO"]
            launch_args[:slowMo] = ENV["E2E_SLOW_MO"].to_i
          end
          playwright.chromium.launch(**launch_args) do |browser|
            context_options = {
              baseURL: "http://0.0.0.0:6503/",
            }
            if ENV["E2E_RECORD_VIDEOS"]
              context_options[:record_video_dir] = Brut.container.tmp_dir / "e2e-videos"
            end
            browser_context = browser.new_context(**context_options)
            browser_context.default_timeout = e2e_timeout
            example.example_group.let(:page) { browser_context.new_page }
            example.run
            browser_context.close
            browser.close
          end
        end
      end
    else
      optional_sidekiq_support.clear_background_jobs
      Sequel::Model.db.transaction do
        inside_db_transaction.()
        example.run
        raise Sequel::Rollback
      end
    end
  end
  @config.after(:suite) do
    Brut::SpecSupport::E2ETestServer.instance.stop
  end
  if @monkey_patch_summary_notification
    monkey_patch_summary_notification!
  end
end