diff --git a/CHANGELOG.md b/CHANGELOG.md
index fafa423b35..31bd0bd0e1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,9 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
### Changed
* [Refactor] [`no-arrow-function-lifecycle`], [`no-unused-class-component-methods`]: use report/messages convention (@ljharb)
+* [Tests] component detection: Add testing scaffolding ([#3149][] @duncanbeevers)
+
+[#3149]: https://github.com/yannickcr/eslint-plugin-react/pull/3149
## [7.27.1] - 2021.11.18
diff --git a/tests/util/Component.js b/tests/util/Component.js
new file mode 100644
index 0000000000..e7e7a747d5
--- /dev/null
+++ b/tests/util/Component.js
@@ -0,0 +1,78 @@
+'use strict';
+
+const assert = require('assert');
+const eslint = require('eslint');
+const Components = require('../../lib/util/Components');
+const parsers = require('../helpers/parsers');
+
+const ruleTester = new eslint.RuleTester({
+ parserOptions: {
+ ecmaVersion: 2018,
+ sourceType: 'module',
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+});
+
+describe('Components', () => {
+ describe('static detect', () => {
+ function testComponentsDetect(test, done) {
+ const rule = Components.detect((context, components, util) => ({
+ 'Program:exit'() {
+ done(context, components, util);
+ },
+ }));
+
+ const tests = {
+ valid: parsers.all([Object.assign({}, test, {
+ settings: {
+ react: {
+ version: 'detect',
+ },
+ },
+ })]),
+ invalid: [],
+ };
+ ruleTester.run(test.code, rule, tests);
+ }
+
+ it('should detect Stateless Function Component', () => {
+ testComponentsDetect({
+ code: `import React from 'react'
+ function MyStatelessComponent() {
+ return ;
+ }`,
+ }, (_context, components) => {
+ assert.equal(components.length(), 1, 'MyStatelessComponent should be detected component');
+ Object.values(components.list()).forEach((component) => {
+ assert.equal(
+ component.node.id.name,
+ 'MyStatelessComponent',
+ 'MyStatelessComponent should be detected component'
+ );
+ });
+ });
+ });
+
+ it('should detect Class Components', () => {
+ testComponentsDetect({
+ code: `import React from 'react'
+ class MyClassComponent extends React.Component {
+ render() {
+ return ;
+ }
+ }`,
+ }, (_context, components) => {
+ assert(components.length() === 1, 'MyClassComponent should be detected component');
+ Object.values(components.list()).forEach((component) => {
+ assert.equal(
+ component.node.id.name,
+ 'MyClassComponent',
+ 'MyClassComponent should be detected component'
+ );
+ });
+ });
+ });
+ });
+});