diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 9836225892b1c..24eef1b596b72 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### Internal
+
+- `CheckboxControl`: Add unit tests ([#41165](https://github.com/WordPress/gutenberg/pull/41165)).
+
## 19.11.0 (2022-05-18)
### Enhancements
diff --git a/packages/components/src/checkbox-control/test/__snapshots__/index.tsx.snap b/packages/components/src/checkbox-control/test/__snapshots__/index.tsx.snap
new file mode 100644
index 0000000000000..408f18d8c7e77
--- /dev/null
+++ b/packages/components/src/checkbox-control/test/__snapshots__/index.tsx.snap
@@ -0,0 +1,42 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CheckboxControl Basic rendering should render the indeterminate icon when in the indeterminate state 1`] = `
+Snapshot Diff:
+- First value
++ Second value
+
+@@ -8,17 +8,31 @@
+
+
++
+
+
+
+
+
+`;
diff --git a/packages/components/src/checkbox-control/test/index.tsx b/packages/components/src/checkbox-control/test/index.tsx
new file mode 100644
index 0000000000000..478a0c8465f9f
--- /dev/null
+++ b/packages/components/src/checkbox-control/test/index.tsx
@@ -0,0 +1,109 @@
+/**
+ * External dependencies
+ */
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { noop } from 'lodash';
+
+/**
+ * WordPress dependencies
+ */
+import { useState } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import BaseCheckboxControl from '..';
+import type { CheckboxControlProps } from '../types';
+
+const getInput = () => screen.getByRole( 'checkbox' ) as HTMLInputElement;
+
+const CheckboxControl = ( props: Omit< CheckboxControlProps, 'onChange' > ) => {
+ return ;
+};
+
+const ControlledCheckboxControl = ( { onChange }: CheckboxControlProps ) => {
+ const [ isChecked, setChecked ] = useState( false );
+ return (
+ {
+ setChecked( value );
+ onChange( value );
+ } }
+ />
+ );
+};
+
+describe( 'CheckboxControl', () => {
+ describe( 'Basic rendering', () => {
+ it( 'should render', () => {
+ render( );
+ expect( getInput() ).not.toBeNull();
+ } );
+
+ it( 'should render an unchecked `checkbox` by default', () => {
+ render( );
+ expect( getInput() ).toHaveProperty( 'checked', false );
+ } );
+
+ it( 'should render an checked `checkbox` when `checked={ true }`', () => {
+ render( );
+ expect( getInput() ).toHaveProperty( 'checked', true );
+ } );
+
+ it( 'should render label', () => {
+ render( );
+
+ const label = screen.getByText( 'Hello' );
+ expect( label ).toBeInTheDocument();
+ } );
+
+ it( 'should render a checkbox in an indeterminate state', () => {
+ render( );
+ expect( getInput() ).toHaveProperty( 'indeterminate', true );
+ } );
+
+ it( 'should render the indeterminate icon when in the indeterminate state', () => {
+ const { container: containerDefault } = render(
+
+ );
+
+ const { container: containerIndeterminate } = render(
+
+ );
+
+ // Expect the diff snapshot to be mostly about the indeterminate icon
+ expect( containerDefault ).toMatchDiffSnapshot(
+ containerIndeterminate
+ );
+ } );
+ } );
+
+ describe( 'Value', () => {
+ it( 'should flip the checked property when clicked', async () => {
+ const user = userEvent.setup( {
+ advanceTimers: jest.advanceTimersByTime,
+ } );
+
+ let state = false;
+ const setState = jest.fn(
+ ( nextState: boolean ) => ( state = nextState )
+ );
+
+ render( );
+
+ const input = getInput();
+
+ await user.click( input );
+ expect( input ).toHaveProperty( 'checked', true );
+ expect( state ).toBe( true );
+
+ await user.click( input );
+ expect( input ).toHaveProperty( 'checked', false );
+ expect( state ).toBe( false );
+
+ expect( setState ).toHaveBeenCalledTimes( 2 );
+ } );
+ } );
+} );