diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index f3b24cdbc46c41718d6af586ee22fb56ceda9a67..975c9d28fb23bad641f7e0652072e62b8ec266e0 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -437,45 +437,6 @@ RSpec/SubjectStub:
     - 'spec/services/unallow_domain_service_spec.rb'
     - 'spec/validators/blacklisted_email_validator_spec.rb'
 
-# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
-RSpec/VerifiedDoubles:
-  Exclude:
-    - 'spec/controllers/admin/change_emails_controller_spec.rb'
-    - 'spec/controllers/admin/confirmations_controller_spec.rb'
-    - 'spec/controllers/admin/disputes/appeals_controller_spec.rb'
-    - 'spec/controllers/admin/domain_allows_controller_spec.rb'
-    - 'spec/controllers/admin/domain_blocks_controller_spec.rb'
-    - 'spec/controllers/api/v1/reports_controller_spec.rb'
-    - 'spec/controllers/api/web/embeds_controller_spec.rb'
-    - 'spec/controllers/auth/sessions_controller_spec.rb'
-    - 'spec/controllers/disputes/appeals_controller_spec.rb'
-    - 'spec/helpers/statuses_helper_spec.rb'
-    - 'spec/lib/suspicious_sign_in_detector_spec.rb'
-    - 'spec/models/account/field_spec.rb'
-    - 'spec/models/session_activation_spec.rb'
-    - 'spec/models/setting_spec.rb'
-    - 'spec/services/account_search_service_spec.rb'
-    - 'spec/services/post_status_service_spec.rb'
-    - 'spec/services/search_service_spec.rb'
-    - 'spec/validators/blacklisted_email_validator_spec.rb'
-    - 'spec/validators/disallowed_hashtags_validator_spec.rb'
-    - 'spec/validators/email_mx_validator_spec.rb'
-    - 'spec/validators/follow_limit_validator_spec.rb'
-    - 'spec/validators/note_length_validator_spec.rb'
-    - 'spec/validators/poll_validator_spec.rb'
-    - 'spec/validators/status_length_validator_spec.rb'
-    - 'spec/validators/status_pin_validator_spec.rb'
-    - 'spec/validators/unique_username_validator_spec.rb'
-    - 'spec/validators/unreserved_username_validator_spec.rb'
-    - 'spec/validators/url_validator_spec.rb'
-    - 'spec/views/statuses/show.html.haml_spec.rb'
-    - 'spec/workers/activitypub/processing_worker_spec.rb'
-    - 'spec/workers/admin/domain_purge_worker_spec.rb'
-    - 'spec/workers/domain_block_worker_spec.rb'
-    - 'spec/workers/domain_clear_media_worker_spec.rb'
-    - 'spec/workers/feed_insert_worker_spec.rb'
-    - 'spec/workers/regeneration_worker_spec.rb'
-
 # This cop supports unsafe autocorrection (--autocorrect-all).
 Rails/ApplicationController:
   Exclude:
diff --git a/spec/controllers/admin/change_emails_controller_spec.rb b/spec/controllers/admin/change_emails_controller_spec.rb
index 503862a7b993d16e23845d0d0f8ff8c6865880a6..dd8a764b64326fe65bacb404f2e3bbb323bdb542 100644
--- a/spec/controllers/admin/change_emails_controller_spec.rb
+++ b/spec/controllers/admin/change_emails_controller_spec.rb
@@ -23,7 +23,8 @@ RSpec.describe Admin::ChangeEmailsController do
 
   describe 'GET #update' do
     before do
-      allow(UserMailer).to receive(:confirmation_instructions).and_return(double('email', deliver_later: nil))
+      allow(UserMailer).to receive(:confirmation_instructions)
+        .and_return(instance_double(ActionMailer::MessageDelivery, deliver_later: nil))
     end
 
     it 'returns http success' do
diff --git a/spec/controllers/admin/confirmations_controller_spec.rb b/spec/controllers/admin/confirmations_controller_spec.rb
index 181616a66e00e7d9c14361bfab680482f343ebfb..95591607867c8b5a914286a26cce4c564d2ae2c6 100644
--- a/spec/controllers/admin/confirmations_controller_spec.rb
+++ b/spec/controllers/admin/confirmations_controller_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe Admin::ConfirmationsController do
     let!(:user) { Fabricate(:user, confirmed_at: confirmed_at) }
 
     before do
-      allow(UserMailer).to receive(:confirmation_instructions) { double(:email, deliver_later: nil) }
+      allow(UserMailer).to receive(:confirmation_instructions) { instance_double(ActionMailer::MessageDelivery, deliver_later: nil) }
     end
 
     context 'when email is not confirmed' do
diff --git a/spec/controllers/admin/disputes/appeals_controller_spec.rb b/spec/controllers/admin/disputes/appeals_controller_spec.rb
index 99b19298c64b7298c24211902f6cb4a23d25f624..3c3f23f5292b6a640554886036c94bcefda60247 100644
--- a/spec/controllers/admin/disputes/appeals_controller_spec.rb
+++ b/spec/controllers/admin/disputes/appeals_controller_spec.rb
@@ -19,7 +19,8 @@ RSpec.describe Admin::Disputes::AppealsController do
     let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
     before do
-      allow(UserMailer).to receive(:appeal_approved).and_return(double('email', deliver_later: nil))
+      allow(UserMailer).to receive(:appeal_approved)
+        .and_return(instance_double(ActionMailer::MessageDelivery, deliver_later: nil))
       post :approve, params: { id: appeal.id }
     end
 
@@ -40,7 +41,8 @@ RSpec.describe Admin::Disputes::AppealsController do
     let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
     before do
-      allow(UserMailer).to receive(:appeal_rejected).and_return(double('email', deliver_later: nil))
+      allow(UserMailer).to receive(:appeal_rejected)
+        .and_return(instance_double(ActionMailer::MessageDelivery, deliver_later: nil))
       post :reject, params: { id: appeal.id }
     end
 
diff --git a/spec/controllers/admin/domain_allows_controller_spec.rb b/spec/controllers/admin/domain_allows_controller_spec.rb
index 6b0453476a9837b9a7151448ee1c933581ee0676..6f82f322b5e3850a13963c21e9ad5fa72c51925a 100644
--- a/spec/controllers/admin/domain_allows_controller_spec.rb
+++ b/spec/controllers/admin/domain_allows_controller_spec.rb
@@ -37,7 +37,7 @@ RSpec.describe Admin::DomainAllowsController do
 
   describe 'DELETE #destroy' do
     it 'disallows the domain' do
-      service = double(call: true)
+      service = instance_double(UnallowDomainService, call: true)
       allow(UnallowDomainService).to receive(:new).and_return(service)
       domain_allow = Fabricate(:domain_allow)
       delete :destroy, params: { id: domain_allow.id }
diff --git a/spec/controllers/admin/domain_blocks_controller_spec.rb b/spec/controllers/admin/domain_blocks_controller_spec.rb
index d499aa64ce3fc16e6ee31647cfe242b431f6e48c..fb7fb2957f4f3d36f97c3f72bdd6628fae4fdc55 100644
--- a/spec/controllers/admin/domain_blocks_controller_spec.rb
+++ b/spec/controllers/admin/domain_blocks_controller_spec.rb
@@ -213,7 +213,7 @@ RSpec.describe Admin::DomainBlocksController do
 
   describe 'DELETE #destroy' do
     it 'unblocks the domain' do
-      service = double(call: true)
+      service = instance_double(UnblockDomainService, call: true)
       allow(UnblockDomainService).to receive(:new).and_return(service)
       domain_block = Fabricate(:domain_block)
       delete :destroy, params: { id: domain_block.id }
diff --git a/spec/controllers/api/v1/reports_controller_spec.rb b/spec/controllers/api/v1/reports_controller_spec.rb
index 0eb9ce17097e96b526f2eff92f60be20b959e301..01b7e4a71c55d2cfd1889012ef2e45b7d054a6b8 100644
--- a/spec/controllers/api/v1/reports_controller_spec.rb
+++ b/spec/controllers/api/v1/reports_controller_spec.rb
@@ -23,7 +23,8 @@ RSpec.describe Api::V1::ReportsController do
     let(:rule_ids) { nil }
 
     before do
-      allow(AdminMailer).to receive(:new_report).and_return(double('email', deliver_later: nil))
+      allow(AdminMailer).to receive(:new_report)
+        .and_return(instance_double(ActionMailer::MessageDelivery, deliver_later: nil))
       post :create, params: { status_ids: [status.id], account_id: target_account.id, comment: 'reasons', category: category, rule_ids: rule_ids, forward: forward }
     end
 
diff --git a/spec/controllers/api/web/embeds_controller_spec.rb b/spec/controllers/api/web/embeds_controller_spec.rb
index b0c48a5aed3b9520b8753214d417c901d289ff3a..8c4e1a8f26bebf8d39a6ebc462e426fe8040c39f 100644
--- a/spec/controllers/api/web/embeds_controller_spec.rb
+++ b/spec/controllers/api/web/embeds_controller_spec.rb
@@ -26,7 +26,7 @@ describe Api::Web::EmbedsController do
 
     context 'when fails to find status' do
       let(:url) { 'https://host.test/oembed.html' }
-      let(:service_instance) { double('fetch_oembed_service') }
+      let(:service_instance) { instance_double(FetchOEmbedService) }
 
       before do
         allow(FetchOEmbedService).to receive(:new) { service_instance }
diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb
index 5b7d5d5cd4e934fb12289e412c47b55f787e4187..c727a763339af97b037b41aba39be438e28db54b 100644
--- a/spec/controllers/auth/sessions_controller_spec.rb
+++ b/spec/controllers/auth/sessions_controller_spec.rb
@@ -127,7 +127,8 @@ RSpec.describe Auth::SessionsController do
 
         before do
           allow_any_instance_of(ActionDispatch::Request).to receive(:remote_ip).and_return(current_ip)
-          allow(UserMailer).to receive(:suspicious_sign_in).and_return(double('email', deliver_later!: nil))
+          allow(UserMailer).to receive(:suspicious_sign_in)
+            .and_return(instance_double(ActionMailer::MessageDelivery, deliver_later!: nil))
           user.update(current_sign_in_at: 1.month.ago)
           post :create, params: { user: { email: user.email, password: user.password } }
         end
diff --git a/spec/controllers/authorize_interactions_controller_spec.rb b/spec/controllers/authorize_interactions_controller_spec.rb
index e521039410a5518466271cc56ffed29740e7a8f1..098c25ba327d22df11273374f6b22e7ab524c793 100644
--- a/spec/controllers/authorize_interactions_controller_spec.rb
+++ b/spec/controllers/authorize_interactions_controller_spec.rb
@@ -28,7 +28,7 @@ describe AuthorizeInteractionsController do
       end
 
       it 'renders error when account cant be found' do
-        service = double
+        service = instance_double(ResolveAccountService)
         allow(ResolveAccountService).to receive(:new).and_return(service)
         allow(service).to receive(:call).with('missing@hostname').and_return(nil)
 
@@ -40,7 +40,7 @@ describe AuthorizeInteractionsController do
 
       it 'sets resource from url' do
         account = Fabricate(:account)
-        service = double
+        service = instance_double(ResolveURLService)
         allow(ResolveURLService).to receive(:new).and_return(service)
         allow(service).to receive(:call).with('http://example.com').and_return(account)
 
@@ -52,7 +52,7 @@ describe AuthorizeInteractionsController do
 
       it 'sets resource from acct uri' do
         account = Fabricate(:account)
-        service = double
+        service = instance_double(ResolveAccountService)
         allow(ResolveAccountService).to receive(:new).and_return(service)
         allow(service).to receive(:call).with('found@hostname').and_return(account)
 
@@ -82,7 +82,7 @@ describe AuthorizeInteractionsController do
       end
 
       it 'shows error when account not found' do
-        service = double
+        service = instance_double(ResolveAccountService)
 
         allow(ResolveAccountService).to receive(:new).and_return(service)
         allow(service).to receive(:call).with('user@hostname').and_return(nil)
@@ -94,7 +94,7 @@ describe AuthorizeInteractionsController do
 
       it 'follows account when found' do
         target_account = Fabricate(:account)
-        service = double
+        service = instance_double(ResolveAccountService)
 
         allow(ResolveAccountService).to receive(:new).and_return(service)
         allow(service).to receive(:call).with('user@hostname').and_return(target_account)
diff --git a/spec/controllers/disputes/appeals_controller_spec.rb b/spec/controllers/disputes/appeals_controller_spec.rb
index d0e1cd3908a10d0c8df2a87681c0c2b21cd11902..a0f9c7b91071adaa005d27cef3a46263f125a23a 100644
--- a/spec/controllers/disputes/appeals_controller_spec.rb
+++ b/spec/controllers/disputes/appeals_controller_spec.rb
@@ -14,7 +14,8 @@ RSpec.describe Disputes::AppealsController do
     let(:strike) { Fabricate(:account_warning, target_account: current_user.account) }
 
     before do
-      allow(AdminMailer).to receive(:new_appeal).and_return(double('email', deliver_later: nil))
+      allow(AdminMailer).to receive(:new_appeal)
+        .and_return(instance_double(ActionMailer::MessageDelivery, deliver_later: nil))
       post :create, params: { strike_id: strike.id, appeal: { text: 'Foo' } }
     end
 
diff --git a/spec/helpers/statuses_helper_spec.rb b/spec/helpers/statuses_helper_spec.rb
index 105da7e1b1e98615335a910d37e91eded976a56c..b7824ca6044477f6752011e879a6ac405743c673 100644
--- a/spec/helpers/statuses_helper_spec.rb
+++ b/spec/helpers/statuses_helper_spec.rb
@@ -117,42 +117,42 @@ describe StatusesHelper do
 
   describe '#style_classes' do
     it do
-      status = double(reblog?: false)
+      status = instance_double(Status, reblog?: false)
       classes = helper.style_classes(status, false, false, false)
 
       expect(classes).to eq 'entry'
     end
 
     it do
-      status = double(reblog?: true)
+      status = instance_double(Status, reblog?: true)
       classes = helper.style_classes(status, false, false, false)
 
       expect(classes).to eq 'entry entry-reblog'
     end
 
     it do
-      status = double(reblog?: false)
+      status = instance_double(Status, reblog?: false)
       classes = helper.style_classes(status, true, false, false)
 
       expect(classes).to eq 'entry entry-predecessor'
     end
 
     it do
-      status = double(reblog?: false)
+      status = instance_double(Status, reblog?: false)
       classes = helper.style_classes(status, false, true, false)
 
       expect(classes).to eq 'entry entry-successor'
     end
 
     it do
-      status = double(reblog?: false)
+      status = instance_double(Status, reblog?: false)
       classes = helper.style_classes(status, false, false, true)
 
       expect(classes).to eq 'entry entry-center'
     end
 
     it do
-      status = double(reblog?: true)
+      status = instance_double(Status, reblog?: true)
       classes = helper.style_classes(status, true, true, true)
 
       expect(classes).to eq 'entry entry-predecessor entry-reblog entry-successor entry-center'
@@ -161,35 +161,35 @@ describe StatusesHelper do
 
   describe '#microformats_classes' do
     it do
-      status = double(reblog?: false)
+      status = instance_double(Status, reblog?: false)
       classes = helper.microformats_classes(status, false, false)
 
       expect(classes).to eq ''
     end
 
     it do
-      status = double(reblog?: false)
+      status = instance_double(Status, reblog?: false)
       classes = helper.microformats_classes(status, true, false)
 
       expect(classes).to eq 'p-in-reply-to'
     end
 
     it do
-      status = double(reblog?: false)
+      status = instance_double(Status, reblog?: false)
       classes = helper.microformats_classes(status, false, true)
 
       expect(classes).to eq 'p-comment'
     end
 
     it do
-      status = double(reblog?: true)
+      status = instance_double(Status, reblog?: true)
       classes = helper.microformats_classes(status, true, false)
 
       expect(classes).to eq 'p-in-reply-to p-repost-of'
     end
 
     it do
-      status = double(reblog?: true)
+      status = instance_double(Status, reblog?: true)
       classes = helper.microformats_classes(status, true, true)
 
       expect(classes).to eq 'p-in-reply-to p-repost-of p-comment'
@@ -198,42 +198,42 @@ describe StatusesHelper do
 
   describe '#microformats_h_class' do
     it do
-      status = double(reblog?: false)
+      status = instance_double(Status, reblog?: false)
       css_class = helper.microformats_h_class(status, false, false, false)
 
       expect(css_class).to eq 'h-entry'
     end
 
     it do
-      status = double(reblog?: true)
+      status = instance_double(Status, reblog?: true)
       css_class = helper.microformats_h_class(status, false, false, false)
 
       expect(css_class).to eq 'h-cite'
     end
 
     it do
-      status = double(reblog?: false)
+      status = instance_double(Status, reblog?: false)
       css_class = helper.microformats_h_class(status, true, false, false)
 
       expect(css_class).to eq 'h-cite'
     end
 
     it do
-      status = double(reblog?: false)
+      status = instance_double(Status, reblog?: false)
       css_class = helper.microformats_h_class(status, false, true, false)
 
       expect(css_class).to eq 'h-cite'
     end
 
     it do
-      status = double(reblog?: false)
+      status = instance_double(Status, reblog?: false)
       css_class = helper.microformats_h_class(status, false, false, true)
 
       expect(css_class).to eq ''
     end
 
     it do
-      status = double(reblog?: true)
+      status = instance_double(Status, reblog?: true)
       css_class = helper.microformats_h_class(status, true, true, true)
 
       expect(css_class).to eq 'h-cite'
diff --git a/spec/lib/activitypub/activity/add_spec.rb b/spec/lib/activitypub/activity/add_spec.rb
index 9c45e465e4897ae88ad645f112a39bd290bfcbdd..ec6df0171666dfcb8050514bae0a7f84f3f6821d 100644
--- a/spec/lib/activitypub/activity/add_spec.rb
+++ b/spec/lib/activitypub/activity/add_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe ActivityPub::Activity::Add do
     end
 
     context 'when status was not known before' do
-      let(:service_stub) { double }
+      let(:service_stub) { instance_double(ActivityPub::FetchRemoteStatusService) }
 
       let(:json) do
         {
diff --git a/spec/lib/activitypub/activity/move_spec.rb b/spec/lib/activitypub/activity/move_spec.rb
index 8bd23aa7bf0b6f52311d4b7f1d02109d3f8e760b..f3973c70ceac015af39d999056e2cf8dd3e52f0a 100644
--- a/spec/lib/activitypub/activity/move_spec.rb
+++ b/spec/lib/activitypub/activity/move_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe ActivityPub::Activity::Move do
     stub_request(:post, old_account.inbox_url).to_return(status: 200)
     stub_request(:post, new_account.inbox_url).to_return(status: 200)
 
-    service_stub = double
+    service_stub = instance_double(ActivityPub::FetchRemoteAccountService)
     allow(ActivityPub::FetchRemoteAccountService).to receive(:new).and_return(service_stub)
     allow(service_stub).to receive(:call).and_return(returned_account)
   end
diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb
index e88631e475971f26742367c2761f2451d1ceee66..f0861376b99da8b6459beb1094c3f189fb97ecfc 100644
--- a/spec/lib/request_spec.rb
+++ b/spec/lib/request_spec.rb
@@ -48,7 +48,7 @@ describe Request do
       end
 
       it 'executes a HTTP request when the first address is private' do
-        resolver = double
+        resolver = instance_double(Resolv::DNS)
 
         allow(resolver).to receive(:getaddresses).with('example.com').and_return(%w(0.0.0.0 2001:4860:4860::8844))
         allow(resolver).to receive(:timeouts=).and_return(nil)
@@ -83,7 +83,7 @@ describe Request do
       end
 
       it 'raises Mastodon::ValidationError' do
-        resolver = double
+        resolver = instance_double(Resolv::DNS)
 
         allow(resolver).to receive(:getaddresses).with('example.com').and_return(%w(0.0.0.0 2001:db8::face))
         allow(resolver).to receive(:timeouts=).and_return(nil)
diff --git a/spec/lib/suspicious_sign_in_detector_spec.rb b/spec/lib/suspicious_sign_in_detector_spec.rb
index c61b1ef1e6647d05e82526a43eba57b9a26fc93e..9e64aff08a16e0ed14d978c59bf736a275a7b967 100644
--- a/spec/lib/suspicious_sign_in_detector_spec.rb
+++ b/spec/lib/suspicious_sign_in_detector_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe SuspiciousSignInDetector do
     subject { described_class.new(user).suspicious?(request) }
 
     let(:user) { Fabricate(:user, current_sign_in_at: 1.day.ago) }
-    let(:request) { double(remote_ip: remote_ip) }
+    let(:request) { instance_double(ActionDispatch::Request, remote_ip: remote_ip) }
     let(:remote_ip) { nil }
 
     context 'when user has 2FA enabled' do
diff --git a/spec/models/account/field_spec.rb b/spec/models/account/field_spec.rb
index 5715a53791735a483c170b39110c3b8ef20e547e..22593bb218babf54387a1381a7e51281e84de80d 100644
--- a/spec/models/account/field_spec.rb
+++ b/spec/models/account/field_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Account::Field do
   describe '#verified?' do
     subject { described_class.new(account, 'name' => 'Foo', 'value' => 'Bar', 'verified_at' => verified_at) }
 
-    let(:account) { double('Account', local?: true) }
+    let(:account) { instance_double(Account, local?: true) }
 
     context 'when verified_at is set' do
       let(:verified_at) { Time.now.utc.iso8601 }
@@ -28,7 +28,7 @@ RSpec.describe Account::Field do
   describe '#mark_verified!' do
     subject { described_class.new(account, original_hash) }
 
-    let(:account) { double('Account', local?: true) }
+    let(:account) { instance_double(Account, local?: true) }
     let(:original_hash) { { 'name' => 'Foo', 'value' => 'Bar' } }
 
     before do
@@ -47,7 +47,7 @@ RSpec.describe Account::Field do
   describe '#verifiable?' do
     subject { described_class.new(account, 'name' => 'Foo', 'value' => value) }
 
-    let(:account) { double('Account', local?: local) }
+    let(:account) { instance_double(Account, local?: local) }
 
     context 'with local accounts' do
       let(:local) { true }
diff --git a/spec/models/account_migration_spec.rb b/spec/models/account_migration_spec.rb
index d76edddd5102306b0fe0839c83d596c83f3aecc1..f4544740b19dfea52d11d22c1da70d6335b97991 100644
--- a/spec/models/account_migration_spec.rb
+++ b/spec/models/account_migration_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe AccountMigration do
       before do
         target_account.aliases.create!(acct: source_account.acct)
 
-        service_double = double
+        service_double = instance_double(ResolveAccountService)
         allow(ResolveAccountService).to receive(:new).and_return(service_double)
         allow(service_double).to receive(:call).with(target_acct, anything).and_return(target_account)
       end
@@ -29,7 +29,7 @@ RSpec.describe AccountMigration do
       let(:target_acct) { 'target@remote' }
 
       before do
-        service_double = double
+        service_double = instance_double(ResolveAccountService)
         allow(ResolveAccountService).to receive(:new).and_return(service_double)
         allow(service_double).to receive(:call).with(target_acct, anything).and_return(nil)
       end
diff --git a/spec/models/session_activation_spec.rb b/spec/models/session_activation_spec.rb
index 052a06e5ca83a53d2873c81925cda8c804247b7e..75842e25bad2230e8c6ef00be5af03263976030e 100644
--- a/spec/models/session_activation_spec.rb
+++ b/spec/models/session_activation_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe SessionActivation do
       allow(session_activation).to receive(:detection).and_return(detection)
     end
 
-    let(:detection)          { double(id: 1) }
+    let(:detection)          { instance_double(Browser::Chrome, id: 1) }
     let(:session_activation) { Fabricate(:session_activation) }
 
     it 'returns detection.id' do
@@ -30,7 +30,7 @@ RSpec.describe SessionActivation do
     end
 
     let(:session_activation) { Fabricate(:session_activation) }
-    let(:detection)          { double(platform: double(id: 1)) }
+    let(:detection)          { instance_double(Browser::Chrome, platform: instance_double(Browser::Platform, id: 1)) }
 
     it 'returns detection.platform.id' do
       expect(session_activation.platform).to be 1
diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb
index bba585cec6824866d4b7a1472f310f1b22e180b6..5ed5c5d766c5ec4c2761b7d6f3d7a3b3842856d9 100644
--- a/spec/models/setting_spec.rb
+++ b/spec/models/setting_spec.rb
@@ -62,7 +62,7 @@ RSpec.describe Setting do
 
         context 'when RailsSettings::Settings.object returns truthy' do
           let(:object) { db_val }
-          let(:db_val) { double(value: 'db_val') }
+          let(:db_val) { instance_double(described_class, value: 'db_val') }
 
           context 'when default_value is a Hash' do
             let(:default_value) { { default_value: 'default_value' } }
diff --git a/spec/services/account_search_service_spec.rb b/spec/services/account_search_service_spec.rb
index 98264e6e1367b51f504f524f6111c175564e2070..1cd036f484887806a9927253305a255b4ec16164 100644
--- a/spec/services/account_search_service_spec.rb
+++ b/spec/services/account_search_service_spec.rb
@@ -53,7 +53,7 @@ describe AccountSearchService, type: :service do
 
     context 'when there is a domain but no exact match' do
       it 'follows the remote account when resolve is true' do
-        service = double(call: nil)
+        service = instance_double(ResolveAccountService, call: nil)
         allow(ResolveAccountService).to receive(:new).and_return(service)
 
         results = subject.call('newuser@remote.com', nil, limit: 10, resolve: true)
@@ -61,7 +61,7 @@ describe AccountSearchService, type: :service do
       end
 
       it 'does not follow the remote account when resolve is false' do
-        service = double(call: nil)
+        service = instance_double(ResolveAccountService, call: nil)
         allow(ResolveAccountService).to receive(:new).and_return(service)
 
         results = subject.call('newuser@remote.com', nil, limit: 10, resolve: false)
diff --git a/spec/services/bootstrap_timeline_service_spec.rb b/spec/services/bootstrap_timeline_service_spec.rb
index 5a15ba74189375c198258fb1e6cd6ffd8c8e96c8..721a0337fd3094218c13b282fcdf0ebc1a40cb27 100644
--- a/spec/services/bootstrap_timeline_service_spec.rb
+++ b/spec/services/bootstrap_timeline_service_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe BootstrapTimelineService, type: :service do
   subject { described_class.new }
 
   context 'when the new user has registered from an invite' do
-    let(:service)    { double }
+    let(:service)    { instance_double(FollowService) }
     let(:autofollow) { false }
     let(:inviter)    { Fabricate(:user, confirmed_at: 2.days.ago) }
     let(:invite)     { Fabricate(:invite, user: inviter, max_uses: nil, expires_at: 1.hour.from_now, autofollow: autofollow) }
diff --git a/spec/services/bulk_import_service_spec.rb b/spec/services/bulk_import_service_spec.rb
index 09dfb0a0b6e2b02ab289991f197687c9fe19e9bb..281b642ea41c2559959b060299d99b4c645bc440 100644
--- a/spec/services/bulk_import_service_spec.rb
+++ b/spec/services/bulk_import_service_spec.rb
@@ -47,7 +47,7 @@ RSpec.describe BulkImportService do
       it 'requests to follow all the listed users once the workers have run' do
         subject.call(import)
 
-        resolve_account_service_double = double
+        resolve_account_service_double = instance_double(ResolveAccountService)
         allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double)
         allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) }
         allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) }
@@ -95,7 +95,7 @@ RSpec.describe BulkImportService do
       it 'requests to follow all the expected users once the workers have run' do
         subject.call(import)
 
-        resolve_account_service_double = double
+        resolve_account_service_double = instance_double(ResolveAccountService)
         allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double)
         allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) }
         allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) }
@@ -133,7 +133,7 @@ RSpec.describe BulkImportService do
       it 'blocks all the listed users once the workers have run' do
         subject.call(import)
 
-        resolve_account_service_double = double
+        resolve_account_service_double = instance_double(ResolveAccountService)
         allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double)
         allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) }
         allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) }
@@ -177,7 +177,7 @@ RSpec.describe BulkImportService do
       it 'requests to follow all the expected users once the workers have run' do
         subject.call(import)
 
-        resolve_account_service_double = double
+        resolve_account_service_double = instance_double(ResolveAccountService)
         allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double)
         allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) }
         allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) }
@@ -215,7 +215,7 @@ RSpec.describe BulkImportService do
       it 'mutes all the listed users once the workers have run' do
         subject.call(import)
 
-        resolve_account_service_double = double
+        resolve_account_service_double = instance_double(ResolveAccountService)
         allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double)
         allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) }
         allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) }
@@ -263,7 +263,7 @@ RSpec.describe BulkImportService do
       it 'requests to follow all the expected users once the workers have run' do
         subject.call(import)
 
-        resolve_account_service_double = double
+        resolve_account_service_double = instance_double(ResolveAccountService)
         allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double)
         allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) }
         allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) }
@@ -360,7 +360,7 @@ RSpec.describe BulkImportService do
       it 'updates the bookmarks as expected once the workers have run' do
         subject.call(import)
 
-        service_double = double
+        service_double = instance_double(ActivityPub::FetchRemoteStatusService)
         allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service_double)
         allow(service_double).to receive(:call).with('https://domain.unknown/foo') { Fabricate(:status, uri: 'https://domain.unknown/foo') }
         allow(service_double).to receive(:call).with('https://domain.unknown/private') { Fabricate(:status, uri: 'https://domain.unknown/private', visibility: :direct) }
@@ -403,7 +403,7 @@ RSpec.describe BulkImportService do
       it 'updates the bookmarks as expected once the workers have run' do
         subject.call(import)
 
-        service_double = double
+        service_double = instance_double(ActivityPub::FetchRemoteStatusService)
         allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service_double)
         allow(service_double).to receive(:call).with('https://domain.unknown/foo') { Fabricate(:status, uri: 'https://domain.unknown/foo') }
         allow(service_double).to receive(:call).with('https://domain.unknown/private') { Fabricate(:status, uri: 'https://domain.unknown/private', visibility: :direct) }
diff --git a/spec/services/fetch_resource_service_spec.rb b/spec/services/fetch_resource_service_spec.rb
index da7e42351744862c45a383399ae5849cfb22acf6..0f1068471f8e38d40b843a58e20a3dba12c3e7f0 100644
--- a/spec/services/fetch_resource_service_spec.rb
+++ b/spec/services/fetch_resource_service_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe FetchResourceService, type: :service do
 
     context 'when OpenSSL::SSL::SSLError is raised' do
       before do
-        request = double
+        request = instance_double(Request)
         allow(Request).to receive(:new).and_return(request)
         allow(request).to receive(:add_headers)
         allow(request).to receive(:on_behalf_of)
@@ -36,7 +36,7 @@ RSpec.describe FetchResourceService, type: :service do
 
     context 'when HTTP::ConnectionError is raised' do
       before do
-        request = double
+        request = instance_double(Request)
         allow(Request).to receive(:new).and_return(request)
         allow(request).to receive(:add_headers)
         allow(request).to receive(:on_behalf_of)
diff --git a/spec/services/import_service_spec.rb b/spec/services/import_service_spec.rb
index 32ba4409c3d0803aa32a8c01bbcfa443c0fd1364..1904ac8dc914a981baf16277bfa98e088de981d4 100644
--- a/spec/services/import_service_spec.rb
+++ b/spec/services/import_service_spec.rb
@@ -219,7 +219,7 @@ RSpec.describe ImportService, type: :service do
     end
 
     before do
-      service = double
+      service = instance_double(ActivityPub::FetchRemoteStatusService)
       allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service)
       allow(service).to receive(:call).with('https://unknown-remote.com/users/bar/statuses/1') do
         Fabricate(:status, uri: 'https://unknown-remote.com/users/bar/statuses/1')
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index 76ef5391f0e0ca34497909a0816d2243b1519a6c..d201292e1724f048bcd21dd3c678f5f84e3922c9 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -132,7 +132,7 @@ RSpec.describe PostStatusService, type: :service do
   end
 
   it 'processes mentions' do
-    mention_service = double(:process_mentions_service)
+    mention_service = instance_double(ProcessMentionsService)
     allow(mention_service).to receive(:call)
     allow(ProcessMentionsService).to receive(:new).and_return(mention_service)
     account = Fabricate(:account)
@@ -163,7 +163,7 @@ RSpec.describe PostStatusService, type: :service do
   end
 
   it 'processes hashtags' do
-    hashtags_service = double(:process_hashtags_service)
+    hashtags_service = instance_double(ProcessHashtagsService)
     allow(hashtags_service).to receive(:call)
     allow(ProcessHashtagsService).to receive(:new).and_return(hashtags_service)
     account = Fabricate(:account)
diff --git a/spec/services/resolve_url_service_spec.rb b/spec/services/resolve_url_service_spec.rb
index 8d2af74173597deac46dd10cf0666868f2bfa3f2..ad5bebb4ed77eb9499ae9318c155c43420d1866e 100644
--- a/spec/services/resolve_url_service_spec.rb
+++ b/spec/services/resolve_url_service_spec.rb
@@ -9,7 +9,7 @@ describe ResolveURLService, type: :service do
     it 'returns nil when there is no resource url' do
       url           = 'http://example.com/missing-resource'
       known_account = Fabricate(:account, uri: url)
-      service = double
+      service = instance_double(FetchResourceService)
 
       allow(FetchResourceService).to receive(:new).and_return service
       allow(service).to receive(:response_code).and_return(404)
@@ -21,7 +21,7 @@ describe ResolveURLService, type: :service do
     it 'returns known account on temporary error' do
       url           = 'http://example.com/missing-resource'
       known_account = Fabricate(:account, uri: url)
-      service = double
+      service = instance_double(FetchResourceService)
 
       allow(FetchResourceService).to receive(:new).and_return service
       allow(service).to receive(:response_code).and_return(500)
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index 00f693dfabb7f1960e0d4a687ec319f876a7f46a..1283a23bf14261ebd2eac0cf1a1ca7526b2bcd91 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -25,7 +25,7 @@ describe SearchService, type: :service do
 
       context 'when it does not find anything' do
         it 'returns the empty results' do
-          service = double(call: nil)
+          service = instance_double(ResolveURLService, call: nil)
           allow(ResolveURLService).to receive(:new).and_return(service)
           results = subject.call(@query, nil, 10, resolve: true)
 
@@ -37,7 +37,7 @@ describe SearchService, type: :service do
       context 'when it finds an account' do
         it 'includes the account in the results' do
           account = Account.new
-          service = double(call: account)
+          service = instance_double(ResolveURLService, call: account)
           allow(ResolveURLService).to receive(:new).and_return(service)
 
           results = subject.call(@query, nil, 10, resolve: true)
@@ -49,7 +49,7 @@ describe SearchService, type: :service do
       context 'when it finds a status' do
         it 'includes the status in the results' do
           status = Status.new
-          service = double(call: status)
+          service = instance_double(ResolveURLService, call: status)
           allow(ResolveURLService).to receive(:new).and_return(service)
 
           results = subject.call(@query, nil, 10, resolve: true)
@@ -64,7 +64,7 @@ describe SearchService, type: :service do
         it 'includes the account in the results' do
           query = 'username'
           account = Account.new
-          service = double(call: [account])
+          service = instance_double(AccountSearchService, call: [account])
           allow(AccountSearchService).to receive(:new).and_return(service)
 
           results = subject.call(query, nil, 10)
diff --git a/spec/services/unsuspend_account_service_spec.rb b/spec/services/unsuspend_account_service_spec.rb
index e02ae41b991e190be4696e659d67a657ea495d12..7ef2630aeb1ab8f4088f440b1558235f46cb1077 100644
--- a/spec/services/unsuspend_account_service_spec.rb
+++ b/spec/services/unsuspend_account_service_spec.rb
@@ -63,7 +63,7 @@ RSpec.describe UnsuspendAccountService, type: :service do
   describe 'unsuspending a remote account' do
     include_examples 'with common context' do
       let!(:account)                 { Fabricate(:account, domain: 'bob.com', uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
-      let!(:resolve_account_service) { double }
+      let!(:resolve_account_service) { instance_double(ResolveAccountService) }
 
       before do
         allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service)
diff --git a/spec/validators/blacklisted_email_validator_spec.rb b/spec/validators/blacklisted_email_validator_spec.rb
index a642405ae616a7fb0fb13cc63ad0cbcb08770a8c..3d3d50f6592cbc0477d7b8cf9602fddb73fb2db5 100644
--- a/spec/validators/blacklisted_email_validator_spec.rb
+++ b/spec/validators/blacklisted_email_validator_spec.rb
@@ -6,8 +6,8 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do
   describe '#validate' do
     subject { described_class.new.validate(user); errors }
 
-    let(:user)   { double(email: 'info@mail.com', sign_up_ip: '1.2.3.4', errors: errors) }
-    let(:errors) { double(add: nil) }
+    let(:user)   { instance_double(User, email: 'info@mail.com', sign_up_ip: '1.2.3.4', errors: errors) }
+    let(:errors) { instance_double(ActiveModel::Errors, add: nil) }
 
     before do
       allow(user).to receive(:valid_invitation?).and_return(false)
diff --git a/spec/validators/disallowed_hashtags_validator_spec.rb b/spec/validators/disallowed_hashtags_validator_spec.rb
index e98db387927d7ead7a4aa68627957bbb1f129a18..7144d2891883859a2a7ba42a9175934811cab8fd 100644
--- a/spec/validators/disallowed_hashtags_validator_spec.rb
+++ b/spec/validators/disallowed_hashtags_validator_spec.rb
@@ -11,8 +11,8 @@ RSpec.describe DisallowedHashtagsValidator, type: :validator do
       described_class.new.validate(status)
     end
 
-    let(:status) { double(errors: errors, local?: local, reblog?: reblog, text: disallowed_tags.map { |x| "##{x}" }.join(' ')) }
-    let(:errors) { double(add: nil) }
+    let(:status) { instance_double(Status, errors: errors, local?: local, reblog?: reblog, text: disallowed_tags.map { |x| "##{x}" }.join(' ')) }
+    let(:errors) { instance_double(ActiveModel::Errors, add: nil) }
 
     context 'with a remote reblog' do
       let(:local)  { false }
diff --git a/spec/validators/email_mx_validator_spec.rb b/spec/validators/email_mx_validator_spec.rb
index d9703d81b172856ac70649023dbc494081af4ada..876d73c18427b7dadb0df82c650bf35c3748c80e 100644
--- a/spec/validators/email_mx_validator_spec.rb
+++ b/spec/validators/email_mx_validator_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 describe EmailMxValidator do
   describe '#validate' do
-    let(:user) { double(email: 'foo@example.com', sign_up_ip: '1.2.3.4', errors: double(add: nil)) }
+    let(:user) { instance_double(User, email: 'foo@example.com', sign_up_ip: '1.2.3.4', errors: instance_double(ActiveModel::Errors, add: nil)) }
 
     context 'with an e-mail domain that is explicitly allowed' do
       around do |block|
@@ -15,7 +15,7 @@ describe EmailMxValidator do
       end
 
       it 'does not add errors if there are no DNS records' do
-        resolver = double
+        resolver = instance_double(Resolv::DNS)
 
         allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([])
         allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([])
@@ -29,7 +29,7 @@ describe EmailMxValidator do
     end
 
     it 'adds no error if there are DNS records for the e-mail domain' do
-      resolver = double
+      resolver = instance_double(Resolv::DNS)
 
       allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([])
       allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([Resolv::DNS::Resource::IN::A.new('192.0.2.42')])
@@ -46,19 +46,19 @@ describe EmailMxValidator do
       allow(TagManager).to receive(:instance).and_return(double)
       allow(double).to receive(:normalize_domain).with('example.com').and_raise(Addressable::URI::InvalidURIError)
 
-      user = double(email: 'foo@example.com', errors: double(add: nil))
+      user = instance_double(User, email: 'foo@example.com', errors: instance_double(ActiveModel::Errors, add: nil))
       subject.validate(user)
       expect(user.errors).to have_received(:add)
     end
 
     it 'adds an error if the domain email portion is blank' do
-      user = double(email: 'foo@', errors: double(add: nil))
+      user = instance_double(User, email: 'foo@', errors: instance_double(ActiveModel::Errors, add: nil))
       subject.validate(user)
       expect(user.errors).to have_received(:add)
     end
 
     it 'adds an error if the email domain name contains empty labels' do
-      resolver = double
+      resolver = instance_double(Resolv::DNS)
 
       allow(resolver).to receive(:getresources).with('example..com', Resolv::DNS::Resource::IN::MX).and_return([])
       allow(resolver).to receive(:getresources).with('example..com', Resolv::DNS::Resource::IN::A).and_return([Resolv::DNS::Resource::IN::A.new('192.0.2.42')])
@@ -66,13 +66,13 @@ describe EmailMxValidator do
       allow(resolver).to receive(:timeouts=).and_return(nil)
       allow(Resolv::DNS).to receive(:open).and_yield(resolver)
 
-      user = double(email: 'foo@example..com', sign_up_ip: '1.2.3.4', errors: double(add: nil))
+      user = instance_double(User, email: 'foo@example..com', sign_up_ip: '1.2.3.4', errors: instance_double(ActiveModel::Errors, add: nil))
       subject.validate(user)
       expect(user.errors).to have_received(:add)
     end
 
     it 'adds an error if there are no DNS records for the e-mail domain' do
-      resolver = double
+      resolver = instance_double(Resolv::DNS)
 
       allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([])
       allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([])
@@ -85,9 +85,11 @@ describe EmailMxValidator do
     end
 
     it 'adds an error if a MX record does not lead to an IP' do
-      resolver = double
+      resolver = instance_double(Resolv::DNS)
 
-      allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([double(exchange: 'mail.example.com')])
+      allow(resolver).to receive(:getresources)
+        .with('example.com', Resolv::DNS::Resource::IN::MX)
+        .and_return([instance_double(Resolv::DNS::Resource::MX, exchange: 'mail.example.com')])
       allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([])
       allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([])
       allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::A).and_return([])
@@ -101,13 +103,15 @@ describe EmailMxValidator do
 
     it 'adds an error if the MX record is blacklisted' do
       EmailDomainBlock.create!(domain: 'mail.example.com')
-      resolver = double
+      resolver = instance_double(Resolv::DNS)
 
-      allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([double(exchange: 'mail.example.com')])
+      allow(resolver).to receive(:getresources)
+        .with('example.com', Resolv::DNS::Resource::IN::MX)
+        .and_return([instance_double(Resolv::DNS::Resource::MX, exchange: 'mail.example.com')])
       allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([])
       allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([])
-      allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::A).and_return([double(address: '2.3.4.5')])
-      allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::AAAA).and_return([double(address: 'fd00::2')])
+      allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::A).and_return([instance_double(Resolv::DNS::Resource::IN::A, address: '2.3.4.5')])
+      allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::AAAA).and_return([instance_double(Resolv::DNS::Resource::IN::A, address: 'fd00::2')])
       allow(resolver).to receive(:timeouts=).and_return(nil)
       allow(Resolv::DNS).to receive(:open).and_yield(resolver)
 
diff --git a/spec/validators/follow_limit_validator_spec.rb b/spec/validators/follow_limit_validator_spec.rb
index 7b9055a27f6d2a9d0d66646b5dfce33d4c7a97be..86b6511d6554f5c1e5f0362f28e1953da8b2a66a 100644
--- a/spec/validators/follow_limit_validator_spec.rb
+++ b/spec/validators/follow_limit_validator_spec.rb
@@ -12,9 +12,9 @@ RSpec.describe FollowLimitValidator, type: :validator do
       described_class.new.validate(follow)
     end
 
-    let(:follow)  { double(account: account, errors: errors) }
-    let(:errors)  { double(add: nil) }
-    let(:account) { double(nil?: _nil, local?: local, following_count: 0, followers_count: 0) }
+    let(:follow)  { instance_double(Follow, account: account, errors: errors) }
+    let(:errors)  { instance_double(ActiveModel::Errors, add: nil) }
+    let(:account) { instance_double(Account, nil?: _nil, local?: local, following_count: 0, followers_count: 0) }
     let(:_nil)    { true }
     let(:local)   { false }
 
diff --git a/spec/validators/note_length_validator_spec.rb b/spec/validators/note_length_validator_spec.rb
index e45d221d7646810bcf8a5c333bd251d193fbd0b0..66fccad3ece3be2248b993d3b67637d0b71a1a01 100644
--- a/spec/validators/note_length_validator_spec.rb
+++ b/spec/validators/note_length_validator_spec.rb
@@ -8,7 +8,7 @@ describe NoteLengthValidator do
   describe '#validate' do
     it 'adds an error when text is over 500 characters' do
       text = 'a' * 520
-      account = double(note: text, errors: double(add: nil))
+      account = instance_double(Account, note: text, errors: activemodel_errors)
 
       subject.validate_each(account, 'note', text)
       expect(account.errors).to have_received(:add)
@@ -16,7 +16,7 @@ describe NoteLengthValidator do
 
     it 'counts URLs as 23 characters flat' do
       text = ('a' * 476) + " http://#{'b' * 30}.com/example"
-      account = double(note: text, errors: double(add: nil))
+      account = instance_double(Account, note: text, errors: activemodel_errors)
 
       subject.validate_each(account, 'note', text)
       expect(account.errors).to_not have_received(:add)
@@ -24,10 +24,16 @@ describe NoteLengthValidator do
 
     it 'does not count non-autolinkable URLs as 23 characters flat' do
       text = ('a' * 476) + "http://#{'b' * 30}.com/example"
-      account = double(note: text, errors: double(add: nil))
+      account = instance_double(Account, note: text, errors: activemodel_errors)
 
       subject.validate_each(account, 'note', text)
       expect(account.errors).to have_received(:add)
     end
+
+    private
+
+    def activemodel_errors
+      instance_double(ActiveModel::Errors, add: nil)
+    end
   end
 end
diff --git a/spec/validators/poll_validator_spec.rb b/spec/validators/poll_validator_spec.rb
index 069a471619796ba748372f21e60a367a2492b6e2..95feb043dbb9c6bed374eb80e1a53aa82ec391ab 100644
--- a/spec/validators/poll_validator_spec.rb
+++ b/spec/validators/poll_validator_spec.rb
@@ -9,8 +9,8 @@ RSpec.describe PollValidator, type: :validator do
     end
 
     let(:validator) { described_class.new }
-    let(:poll) { double(options: options, expires_at: expires_at, errors: errors) }
-    let(:errors) { double(add: nil) }
+    let(:poll) { instance_double(Poll, options: options, expires_at: expires_at, errors: errors) }
+    let(:errors) { instance_double(ActiveModel::Errors, add: nil) }
     let(:options) { %w(foo bar) }
     let(:expires_at) { 1.day.from_now }
 
diff --git a/spec/validators/status_length_validator_spec.rb b/spec/validators/status_length_validator_spec.rb
index e132b5618a5e5f958f7cc61343f9b11b45bdb6d7..98ea15e03bb39835ab4c96cbc0acfd09d69bbc6e 100644
--- a/spec/validators/status_length_validator_spec.rb
+++ b/spec/validators/status_length_validator_spec.rb
@@ -5,38 +5,38 @@ require 'rails_helper'
 describe StatusLengthValidator do
   describe '#validate' do
     it 'does not add errors onto remote statuses' do
-      status = double(local?: false)
+      status = instance_double(Status, local?: false)
       subject.validate(status)
       expect(status).to_not receive(:errors)
     end
 
     it 'does not add errors onto local reblogs' do
-      status = double(local?: false, reblog?: true)
+      status = instance_double(Status, local?: false, reblog?: true)
       subject.validate(status)
       expect(status).to_not receive(:errors)
     end
 
     it 'adds an error when content warning is over 500 characters' do
-      status = double(spoiler_text: 'a' * 520, text: '', errors: double(add: nil), local?: true, reblog?: false)
+      status = instance_double(Status, spoiler_text: 'a' * 520, text: '', errors: activemodel_errors, local?: true, reblog?: false)
       subject.validate(status)
       expect(status.errors).to have_received(:add)
     end
 
     it 'adds an error when text is over 500 characters' do
-      status = double(spoiler_text: '', text: 'a' * 520, errors: double(add: nil), local?: true, reblog?: false)
+      status = instance_double(Status, spoiler_text: '', text: 'a' * 520, errors: activemodel_errors, local?: true, reblog?: false)
       subject.validate(status)
       expect(status.errors).to have_received(:add)
     end
 
     it 'adds an error when text and content warning are over 500 characters total' do
-      status = double(spoiler_text: 'a' * 250, text: 'b' * 251, errors: double(add: nil), local?: true, reblog?: false)
+      status = instance_double(Status, spoiler_text: 'a' * 250, text: 'b' * 251, errors: activemodel_errors, local?: true, reblog?: false)
       subject.validate(status)
       expect(status.errors).to have_received(:add)
     end
 
     it 'counts URLs as 23 characters flat' do
       text   = ('a' * 476) + " http://#{'b' * 30}.com/example"
-      status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false)
+      status = instance_double(Status, spoiler_text: '', text: text, errors: activemodel_errors, local?: true, reblog?: false)
 
       subject.validate(status)
       expect(status.errors).to_not have_received(:add)
@@ -44,7 +44,7 @@ describe StatusLengthValidator do
 
     it 'does not count non-autolinkable URLs as 23 characters flat' do
       text   = ('a' * 476) + "http://#{'b' * 30}.com/example"
-      status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false)
+      status = instance_double(Status, spoiler_text: '', text: text, errors: activemodel_errors, local?: true, reblog?: false)
 
       subject.validate(status)
       expect(status.errors).to have_received(:add)
@@ -52,14 +52,14 @@ describe StatusLengthValidator do
 
     it 'does not count overly long URLs as 23 characters flat' do
       text = "http://example.com/valid?#{'#foo?' * 1000}"
-      status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false)
+      status = instance_double(Status, spoiler_text: '', text: text, errors: activemodel_errors, local?: true, reblog?: false)
       subject.validate(status)
       expect(status.errors).to have_received(:add)
     end
 
     it 'counts only the front part of remote usernames' do
       text   = ('a' * 475) + " @alice@#{'b' * 30}.com"
-      status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false)
+      status = instance_double(Status, spoiler_text: '', text: text, errors: activemodel_errors, local?: true, reblog?: false)
 
       subject.validate(status)
       expect(status.errors).to_not have_received(:add)
@@ -67,10 +67,16 @@ describe StatusLengthValidator do
 
     it 'does count both parts of remote usernames for overly long domains' do
       text   = "@alice@#{'b' * 500}.com"
-      status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false)
+      status = instance_double(Status, spoiler_text: '', text: text, errors: activemodel_errors, local?: true, reblog?: false)
 
       subject.validate(status)
       expect(status.errors).to have_received(:add)
     end
   end
+
+  private
+
+  def activemodel_errors
+    instance_double(ActiveModel::Errors, add: nil)
+  end
 end
diff --git a/spec/validators/status_pin_validator_spec.rb b/spec/validators/status_pin_validator_spec.rb
index 00b89d702fd79e51ebbce8da9f5d4ac1008f00f6..e8f8a45434896ab74f245988caad97117c54a904 100644
--- a/spec/validators/status_pin_validator_spec.rb
+++ b/spec/validators/status_pin_validator_spec.rb
@@ -8,11 +8,11 @@ RSpec.describe StatusPinValidator, type: :validator do
       subject.validate(pin)
     end
 
-    let(:pin) { double(account: account, errors: errors, status: status, account_id: pin_account_id) }
-    let(:status) { double(reblog?: reblog, account_id: status_account_id, visibility: visibility, direct_visibility?: visibility == 'direct') }
-    let(:account)     { double(status_pins: status_pins, local?: local) }
-    let(:status_pins) { double(count: count) }
-    let(:errors)      { double(add: nil) }
+    let(:pin) { instance_double(StatusPin, account: account, errors: errors, status: status, account_id: pin_account_id) }
+    let(:status) { instance_double(Status, reblog?: reblog, account_id: status_account_id, visibility: visibility, direct_visibility?: visibility == 'direct') }
+    let(:account)     { instance_double(Account, status_pins: status_pins, local?: local) }
+    let(:status_pins) { instance_double(Array, count: count) }
+    let(:errors)      { instance_double(ActiveModel::Errors, add: nil) }
     let(:pin_account_id)    { 1 }
     let(:status_account_id) { 1 }
     let(:visibility)  { 'public' }
diff --git a/spec/validators/unique_username_validator_spec.rb b/spec/validators/unique_username_validator_spec.rb
index 6867cbc6ce2b6171d4f7e8bbc897f033f22290ad..0d172c8408959d2175ceedc3136bdd1dc60ea523 100644
--- a/spec/validators/unique_username_validator_spec.rb
+++ b/spec/validators/unique_username_validator_spec.rb
@@ -6,7 +6,7 @@ describe UniqueUsernameValidator do
   describe '#validate' do
     context 'when local account' do
       it 'does not add errors if username is nil' do
-        account = double(username: nil, domain: nil, persisted?: false, errors: double(add: nil))
+        account = instance_double(Account, username: nil, domain: nil, persisted?: false, errors: activemodel_errors)
         subject.validate(account)
         expect(account.errors).to_not have_received(:add)
       end
@@ -18,14 +18,14 @@ describe UniqueUsernameValidator do
 
       it 'adds an error when the username is already used with ignoring cases' do
         Fabricate(:account, username: 'ABCdef')
-        account = double(username: 'abcDEF', domain: nil, persisted?: false, errors: double(add: nil))
+        account = instance_double(Account, username: 'abcDEF', domain: nil, persisted?: false, errors: activemodel_errors)
         subject.validate(account)
         expect(account.errors).to have_received(:add)
       end
 
       it 'does not add errors when same username remote account exists' do
         Fabricate(:account, username: 'abcdef', domain: 'example.com')
-        account = double(username: 'abcdef', domain: nil, persisted?: false, errors: double(add: nil))
+        account = instance_double(Account, username: 'abcdef', domain: nil, persisted?: false, errors: activemodel_errors)
         subject.validate(account)
         expect(account.errors).to_not have_received(:add)
       end
@@ -34,7 +34,7 @@ describe UniqueUsernameValidator do
 
   context 'when remote account' do
     it 'does not add errors if username is nil' do
-      account = double(username: nil, domain: 'example.com', persisted?: false, errors: double(add: nil))
+      account = instance_double(Account, username: nil, domain: 'example.com', persisted?: false, errors: activemodel_errors)
       subject.validate(account)
       expect(account.errors).to_not have_received(:add)
     end
@@ -46,23 +46,29 @@ describe UniqueUsernameValidator do
 
     it 'adds an error when the username is already used with ignoring cases' do
       Fabricate(:account, username: 'ABCdef', domain: 'example.com')
-      account = double(username: 'abcDEF', domain: 'example.com', persisted?: false, errors: double(add: nil))
+      account = instance_double(Account, username: 'abcDEF', domain: 'example.com', persisted?: false, errors: activemodel_errors)
       subject.validate(account)
       expect(account.errors).to have_received(:add)
     end
 
     it 'adds an error when the domain is already used with ignoring cases' do
       Fabricate(:account, username: 'ABCdef', domain: 'example.com')
-      account = double(username: 'ABCdef', domain: 'EXAMPLE.COM', persisted?: false, errors: double(add: nil))
+      account = instance_double(Account, username: 'ABCdef', domain: 'EXAMPLE.COM', persisted?: false, errors: activemodel_errors)
       subject.validate(account)
       expect(account.errors).to have_received(:add)
     end
 
     it 'does not add errors when account with the same username and another domain exists' do
       Fabricate(:account, username: 'abcdef', domain: 'example.com')
-      account = double(username: 'abcdef', domain: 'example2.com', persisted?: false, errors: double(add: nil))
+      account = instance_double(Account, username: 'abcdef', domain: 'example2.com', persisted?: false, errors: activemodel_errors)
       subject.validate(account)
       expect(account.errors).to_not have_received(:add)
     end
   end
+
+  private
+
+  def activemodel_errors
+    instance_double(ActiveModel::Errors, add: nil)
+  end
 end
diff --git a/spec/validators/unreserved_username_validator_spec.rb b/spec/validators/unreserved_username_validator_spec.rb
index 85bd7dcb6abb85ff48582ac883621b0987444e20..6f353eeafdc5171e8633336e972fbbe60fea2763 100644
--- a/spec/validators/unreserved_username_validator_spec.rb
+++ b/spec/validators/unreserved_username_validator_spec.rb
@@ -10,8 +10,8 @@ RSpec.describe UnreservedUsernameValidator, type: :validator do
     end
 
     let(:validator) { described_class.new }
-    let(:account)   { double(username: username, errors: errors) }
-    let(:errors) { double(add: nil) }
+    let(:account)   { instance_double(Account, username: username, errors: errors) }
+    let(:errors) { instance_double(ActiveModel::Errors, add: nil) }
 
     context 'when @username is blank?' do
       let(:username) { nil }
diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb
index a56ccd8e08864ab0e101fd410836724f18b492f1..f2220e32b04edde6290e8f4e4d14f60b42d4227b 100644
--- a/spec/validators/url_validator_spec.rb
+++ b/spec/validators/url_validator_spec.rb
@@ -10,8 +10,8 @@ RSpec.describe URLValidator, type: :validator do
     end
 
     let(:validator) { described_class.new(attributes: [attribute]) }
-    let(:record)    { double(errors: errors) }
-    let(:errors)    { double(add: nil) }
+    let(:record)    { instance_double(Webhook, errors: errors) }
+    let(:errors)    { instance_double(ActiveModel::Errors, add: nil) }
     let(:value)     { '' }
     let(:attribute) { :foo }
 
diff --git a/spec/views/statuses/show.html.haml_spec.rb b/spec/views/statuses/show.html.haml_spec.rb
index 370743dfec37a4664b19afe638f2cc35aa6acd22..06f5132d9f5d8a9cd64fed18e025c5bab029e920 100644
--- a/spec/views/statuses/show.html.haml_spec.rb
+++ b/spec/views/statuses/show.html.haml_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 describe 'statuses/show.html.haml', without_verify_partial_doubles: true do
   before do
-    double(api_oembed_url: '')
+    allow(view).to receive(:api_oembed_url).and_return('')
     allow(view).to receive(:show_landing_strip?).and_return(true)
     allow(view).to receive(:site_title).and_return('example site')
     allow(view).to receive(:site_hostname).and_return('example.com')
diff --git a/spec/workers/activitypub/processing_worker_spec.rb b/spec/workers/activitypub/processing_worker_spec.rb
index 6b57f16a9233556d6c0d9cca3b5b1b658ea382b1..66d1cf48904b9e978b7afc273657d487b1608a53 100644
--- a/spec/workers/activitypub/processing_worker_spec.rb
+++ b/spec/workers/activitypub/processing_worker_spec.rb
@@ -9,7 +9,8 @@ describe ActivityPub::ProcessingWorker do
 
   describe '#perform' do
     it 'delegates to ActivityPub::ProcessCollectionService' do
-      allow(ActivityPub::ProcessCollectionService).to receive(:new).and_return(double(:service, call: nil))
+      allow(ActivityPub::ProcessCollectionService).to receive(:new)
+        .and_return(instance_double(ActivityPub::ProcessCollectionService, call: nil))
       subject.perform(account.id, '')
       expect(ActivityPub::ProcessCollectionService).to have_received(:new)
     end
diff --git a/spec/workers/admin/domain_purge_worker_spec.rb b/spec/workers/admin/domain_purge_worker_spec.rb
index b67c58b234589f36d83476c9c89e5b9ce65ea5fc..861fd71a7f91d590455548820560f3a53fccfbb2 100644
--- a/spec/workers/admin/domain_purge_worker_spec.rb
+++ b/spec/workers/admin/domain_purge_worker_spec.rb
@@ -7,7 +7,7 @@ describe Admin::DomainPurgeWorker do
 
   describe 'perform' do
     it 'calls domain purge service for relevant domain block' do
-      service = double(call: nil)
+      service = instance_double(PurgeDomainService, call: nil)
       allow(PurgeDomainService).to receive(:new).and_return(service)
       result = subject.perform('example.com')
 
diff --git a/spec/workers/domain_block_worker_spec.rb b/spec/workers/domain_block_worker_spec.rb
index 8b98443fa7d7bcf39cfdcf01b48093bc4ff6e521..33c3ca009ac32af7e5fe9a6021c09fa71e383096 100644
--- a/spec/workers/domain_block_worker_spec.rb
+++ b/spec/workers/domain_block_worker_spec.rb
@@ -9,7 +9,7 @@ describe DomainBlockWorker do
     let(:domain_block) { Fabricate(:domain_block) }
 
     it 'calls domain block service for relevant domain block' do
-      service = double(call: nil)
+      service = instance_double(BlockDomainService, call: nil)
       allow(BlockDomainService).to receive(:new).and_return(service)
       result = subject.perform(domain_block.id)
 
diff --git a/spec/workers/domain_clear_media_worker_spec.rb b/spec/workers/domain_clear_media_worker_spec.rb
index f21d1fe189c74711ded1e210e439e2939454f3ae..21f8f87b2f615c638b15cad8714aed47e6542cb7 100644
--- a/spec/workers/domain_clear_media_worker_spec.rb
+++ b/spec/workers/domain_clear_media_worker_spec.rb
@@ -9,7 +9,7 @@ describe DomainClearMediaWorker do
     let(:domain_block) { Fabricate(:domain_block, severity: :silence, reject_media: true) }
 
     it 'calls domain clear media service for relevant domain block' do
-      service = double(call: nil)
+      service = instance_double(ClearDomainMediaService, call: nil)
       allow(ClearDomainMediaService).to receive(:new).and_return(service)
       result = subject.perform(domain_block.id)
 
diff --git a/spec/workers/feed_insert_worker_spec.rb b/spec/workers/feed_insert_worker_spec.rb
index 16f7d73e02548d4fd795f0363234730f811a1272..97c73c5999fd2aef9e96ff308b4261da7e46c6af 100644
--- a/spec/workers/feed_insert_worker_spec.rb
+++ b/spec/workers/feed_insert_worker_spec.rb
@@ -11,7 +11,7 @@ describe FeedInsertWorker do
 
     context 'when there are no records' do
       it 'skips push with missing status' do
-        instance = double(push_to_home: nil)
+        instance = instance_double(FeedManager, push_to_home: nil)
         allow(FeedManager).to receive(:instance).and_return(instance)
         result = subject.perform(nil, follower.id)
 
@@ -20,7 +20,7 @@ describe FeedInsertWorker do
       end
 
       it 'skips push with missing account' do
-        instance = double(push_to_home: nil)
+        instance = instance_double(FeedManager, push_to_home: nil)
         allow(FeedManager).to receive(:instance).and_return(instance)
         result = subject.perform(status.id, nil)
 
@@ -31,7 +31,7 @@ describe FeedInsertWorker do
 
     context 'when there are real records' do
       it 'skips the push when there is a filter' do
-        instance = double(push_to_home: nil, filter?: true)
+        instance = instance_double(FeedManager, push_to_home: nil, filter?: true)
         allow(FeedManager).to receive(:instance).and_return(instance)
         result = subject.perform(status.id, follower.id)
 
@@ -40,7 +40,7 @@ describe FeedInsertWorker do
       end
 
       it 'pushes the status onto the home timeline without filter' do
-        instance = double(push_to_home: nil, filter?: false)
+        instance = instance_double(FeedManager, push_to_home: nil, filter?: false)
         allow(FeedManager).to receive(:instance).and_return(instance)
         result = subject.perform(status.id, follower.id)
 
diff --git a/spec/workers/move_worker_spec.rb b/spec/workers/move_worker_spec.rb
index ac7bd506b63efcfbd268748419ff756b6fb25ce6..7577f6e896847f12600b7f51a85e01c76235c15e 100644
--- a/spec/workers/move_worker_spec.rb
+++ b/spec/workers/move_worker_spec.rb
@@ -15,7 +15,7 @@ describe MoveWorker do
   let!(:account_note)    { Fabricate(:account_note, account: local_user.account, target_account: source_account, comment: comment) }
   let(:list)             { Fabricate(:list, account: local_follower) }
 
-  let(:block_service) { double }
+  let(:block_service) { instance_double(BlockService) }
 
   before do
     stub_request(:post, 'https://example.org/a/inbox').to_return(status: 200)
diff --git a/spec/workers/publish_scheduled_announcement_worker_spec.rb b/spec/workers/publish_scheduled_announcement_worker_spec.rb
index 0977bba1ee157a9d17e9495a3af959175621d143..2e50d4a50da3985ae69b2663159f0ccd40f6e06f 100644
--- a/spec/workers/publish_scheduled_announcement_worker_spec.rb
+++ b/spec/workers/publish_scheduled_announcement_worker_spec.rb
@@ -12,7 +12,7 @@ describe PublishScheduledAnnouncementWorker do
 
   describe 'perform' do
     before do
-      service = double
+      service = instance_double(FetchRemoteStatusService)
       allow(FetchRemoteStatusService).to receive(:new).and_return(service)
       allow(service).to receive(:call).with('https://domain.com/users/foo/12345') { remote_status.reload }
 
diff --git a/spec/workers/refollow_worker_spec.rb b/spec/workers/refollow_worker_spec.rb
index 1dac15385be2163f5a2edcf7afd66234f589ef59..5718d4db4976525c6445f5c1976ba838949b2969 100644
--- a/spec/workers/refollow_worker_spec.rb
+++ b/spec/workers/refollow_worker_spec.rb
@@ -10,7 +10,7 @@ describe RefollowWorker do
   let(:bob)     { Fabricate(:account, domain: nil, username: 'bob') }
 
   describe 'perform' do
-    let(:service) { double }
+    let(:service) { instance_double(FollowService) }
 
     before do
       allow(FollowService).to receive(:new).and_return(service)
diff --git a/spec/workers/regeneration_worker_spec.rb b/spec/workers/regeneration_worker_spec.rb
index 147a76be50ec767425384e19168d6f10f1cc7c29..37b0a04c49fb5b69da756538f10b5285660e3f73 100644
--- a/spec/workers/regeneration_worker_spec.rb
+++ b/spec/workers/regeneration_worker_spec.rb
@@ -9,7 +9,7 @@ describe RegenerationWorker do
     let(:account) { Fabricate(:account) }
 
     it 'calls the precompute feed service for the account' do
-      service = double(call: nil)
+      service = instance_double(PrecomputeFeedService, call: nil)
       allow(PrecomputeFeedService).to receive(:new).and_return(service)
       result = subject.perform(account.id)