What is Spec
?
Spec is a specification of behavior that we record and replay.
It is identified by the specName
, which you defined when using the mocktomata:
mockto('<specName>', ...)
komondor('<specName>')
scenario('<specName>') // zucchini
Uniqueness of specName
Because specName
is the identifier of Spec,
the specName
must be unique for each test in the same file.
For example, if you use "get friends"
as the specName
in two test of a test file,
one of them will fail.
This holds true even if you use some grouping mechanism such as describe()
to put tests in different group.
However, it will be fine if the same specName
is used in two different test files.
So what you can do is either making sure you use a different specName
in the same file across describe()
groups,
or you can separate each group into different files. For example:
- someCode.group-1.spec.ts
- someCode.group-2.spec.ts
This is also the way tap
family of test runners (e.g. ava
) organize the tests.
What can be a spec subject
?
spec subject is the code you want to record the behavior from.
The most common use case is a gateway
function or library,
such as axios
or fetch
.
But it can be any other things, such as gateway
to your database,
file system, another thread,
or even libraries or code from another team that might not be stable.
In essence, it can be anything that is external, suspect to chanages, or anything unstable.
SpecRecord
SpecRecord is the record of a Spec that we saved.
It is saved in the .mocktomata
folder under the root of your project.
In the future, they can also be saved remotely too.
The done()
method returns the SpecRecord,
so you can inspect it if you wanted to.
spec()
spec()
function is the function to specify the spec subject.
function spec(subject, options?): Promise<subject>
Where the return promise contains the "speced" subject which you can use in place of subject
.
You can specify a mock
using the options
:
spec(subject, { mock: mockSubject })
When running in mock
mode (e.g. mockto.mock(...)
),
the mockSubject
will be used instead of the subject
.
In other modes, the mockSubject
will be ignored.
This is useful to write a test with a mock subject, while the actual subject/remote system is not ready yet.
Spec.Options
Spec.Options
is used by the mocktomata
to configure the spec. It contains 3 properties:
timeout
: How long will the spec wait before consider the subject failed to return. (default to3000
ms)logLevel
: Log level for logging the behavior (default tologLevels.info
)emitLog
: When true, emit logs to console (defaultfalse
)
Here is how do specify the options
for other mocktomata:
mockto(specName, options?, handler)
komondor(specName, options?)
scenario(specName, options?) // zucchini
incubator(specName, options?, handler)
reporter
reporter
is a MemoryLogReporter from standard-log.
It contains the logs generated by mocktomata so that you can inspect them.
A common use case is to inspect the logs make sure it does not contain any sensitive information.
SpecMode
Each Spec runs in a specific mode depends on the condition.
There are 4 different SpecMode
:
auto
: the default mode which will resolve to eithersave
orsimulate
mode.save
: actual calls will be made and the behavior are saved (i.e. recording).simulate
: actual calls are not made and the behavior are simulated (i.e. replay).live
: actual calls are made but the behavior are not saved.
done()
done()
should be called at the end of each Spec.
It is how the Spec knows it should save the SpecRecord or make sure the simulation are complete fully.
It is an async function returning the SpecRecord (done(): Promise<SpecRecord>
).
You can inspec the SpecRecord if you want to,
but typically you will just await
or return it:
it('...', async () => {
// ...
await done()
// or return, if your test-runner support that.
return done()
}
cleanup()
cleanup()
function is used to clean up the test environment.
You should call it at afterAll()
or similar function:
afterall(mockto.cleanup)
afterall(komondor.cleanup)
afterall(scenario.cleanup) // zucchini
afterall(incubator.cleanup)
What happens is when the done()
is not called,
either you forget to do so or the test fails,
the Spec does not know that and keep waiting for the done()
call.
In that case you will see a warning like this (in jest
):
A worker process has failed to exit gracefully and has been force exited.
This is likely caused by tests leaking due to improper teardown.
Try running with --detectOpenHandles to find leaks.
Active timers can also cause this, ensure that `.unref()` was called on them.
calling cleanup()
tells mocktomata that all Spec are done so it can clean things up properly.
maskValue()
When interacting with external system, the calls may contain sensitive information such as username, password, auth token, etc.
Since mocktomata will save the SpecRecord, it is important to make sure these sensitive information will not be saved in your repository. The same goes for logs, as you may send the logs else where or save it in some files.
maskValue()
allows you to indicate what values are considered sensitive,
and mask them in the logs and in SpecRecord.
It support string
and RegExp
, and its signature is:
function maskValue(value: string | RegExp, replaceWith?: string)
The RegExp
will be .exec()
against the values,
so if the sensitive information can occurs multiple times in the same string,
use the /g
global flag to make sure all occurrences are masked:
maskValue(/some-secret/g)
maskValue(/Bearer \.*/g)
The replaceWith
defaults to "[masked]"
.
You can change that if you want,
and you can change it different values for each maskValue()
call if you find that beneficial.
You must call maskValue()
before calling spec()
,
because calling spec()
will starting emitting/saving logs.
Depends on how the sensitive information is created, who created it, and what SpecMode the Spec is running in, the value flow through your code could be the actual sensitive value, or the masked value.
so in your test, you should avoid checking the value explicitly.
Your code will probably work as-is.
If you absolutely need to,
you can use the replaceWith
to set the masked value to something your code understand,
and yet the value is safe to be saved in your source control.
ignoreMismatch()
Sometimes the input you provide to the test may not be static. For example, random number, date time, or hostnames.
In 7.0, reference values (string, object, array, function) are not validate for changes,
i.e. even if you pass in a different string, the system will not fail it with ActionMismatch
error.
This is because we track the behavior, not and exact call, whenever possible.
This is a new concept and is subject to change if it does not work well.
On the other hand, primitive values such as number and boolean are validated.
If you want to tell spec()
that it should not care about the changes,
you can use ignoreMismatch()
to ignore that specific value.
The value is compared using strict equivalence. (i.e. ===
).
For example:
test('...', async () => {
const date = new Date()
const spec = komondor('...')
spec.ignoreMismatch(date)
// ...
})