@@ -85,6 +85,7 @@ const {
8585 testOnlyFlag,
8686} = parseCommandLine ( ) ;
8787let kResistStopPropagation ;
88+ let assertObj ;
8889let findSourceMap ;
8990
9091function lazyFindSourceMap ( file ) {
@@ -95,6 +96,34 @@ function lazyFindSourceMap(file) {
9596 return findSourceMap ( file ) ;
9697}
9798
99+ function lazyAssertObject ( ) {
100+ if ( assertObj === undefined ) {
101+ const assert = require ( 'assert' ) ;
102+
103+ assertObj = new SafeMap ( [
104+ [ 'deepEqual' , assert . deepEqual ] ,
105+ [ 'deepStrictEqual' , assert . deepStrictEqual ] ,
106+ [ 'doesNotMatch' , assert . doesNotMatch ] ,
107+ [ 'doesNotReject' , assert . doesNotReject ] ,
108+ [ 'doesNotThrow' , assert . doesNotThrow ] ,
109+ [ 'equal' , assert . equal ] ,
110+ [ 'fail' , assert . fail ] ,
111+ [ 'ifError' , assert . ifError ] ,
112+ [ 'match' , assert . match ] ,
113+ [ 'notDeepEqual' , assert . notDeepEqual ] ,
114+ [ 'notDeepStrictEqual' , assert . notDeepStrictEqual ] ,
115+ [ 'notEqual' , assert . notEqual ] ,
116+ [ 'notStrictEqual' , assert . notStrictEqual ] ,
117+ [ 'ok' , assert . ok ] ,
118+ [ 'rejects' , assert . rejects ] ,
119+ [ 'strictEqual' , assert . strictEqual ] ,
120+ [ 'throws' , assert . throws ] ,
121+ ] ) ;
122+ }
123+
124+ return assertObj ;
125+ }
126+
98127function stopTest ( timeout , signal ) {
99128 const deferred = createDeferredPromise ( ) ;
100129 const abortListener = addAbortListener ( signal , deferred . resolve ) ;
@@ -134,7 +163,25 @@ function stopTest(timeout, signal) {
134163 return deferred . promise ;
135164}
136165
166+ class TestPlan {
167+ constructor ( count ) {
168+ validateUint32 ( count , 'count' , 0 ) ;
169+ this . expected = count ;
170+ this . actual = 0 ;
171+ }
172+
173+ check ( ) {
174+ if ( this . actual !== this . expected ) {
175+ throw new ERR_TEST_FAILURE (
176+ `plan expected ${ this . expected } assertions but received ${ this . actual } ` ,
177+ kTestCodeFailure ,
178+ ) ;
179+ }
180+ }
181+ }
182+
137183class TestContext {
184+ #assert;
138185 #test;
139186
140187 constructor ( test ) {
@@ -161,6 +208,38 @@ class TestContext {
161208 this . #test. diagnostic ( message ) ;
162209 }
163210
211+ plan ( count ) {
212+ if ( this . #test. plan !== null ) {
213+ throw new ERR_TEST_FAILURE (
214+ 'cannot set plan more than once' ,
215+ kTestCodeFailure ,
216+ ) ;
217+ }
218+
219+ this . #test. plan = new TestPlan ( count ) ;
220+ }
221+
222+ get assert ( ) {
223+ if ( this . #assert === undefined ) {
224+ const { plan } = this . #test;
225+ const map = lazyAssertObject ( ) ;
226+ const assert = { __proto__ : null } ;
227+
228+ this . #assert = assert ;
229+ map . forEach ( ( method , name ) => {
230+ assert [ name ] = function ( ...args ) {
231+ if ( plan !== null ) {
232+ plan . actual ++ ;
233+ }
234+
235+ return ReflectApply ( method , assert , args ) ;
236+ } ;
237+ } ) ;
238+ }
239+
240+ return this . #assert;
241+ }
242+
164243 get mock ( ) {
165244 this . #test. mock ??= new MockTracker ( ) ;
166245 return this . #test. mock ;
@@ -347,6 +426,7 @@ class Test extends AsyncResource {
347426 this . fn = fn ;
348427 this . harness = null ; // Configured on the root test by the test harness.
349428 this . mock = null ;
429+ this . plan = null ;
350430 this . cancelled = false ;
351431 this . skipped = skip !== undefined && skip !== false ;
352432 this . isTodo = todo !== undefined && todo !== false ;
@@ -708,6 +788,7 @@ class Test extends AsyncResource {
708788 return ;
709789 }
710790
791+ this . plan ?. check ( ) ;
711792 this . pass ( ) ;
712793 try {
713794 await afterEach ( ) ;
0 commit comments