GlucoseIQ

Testing

Generate repeatable CGM series and scenario fixtures with @glucoseiq/testing.

@glucoseiq/testing produces repeatable CGM data for unit tests, Storybook stories, product demos, screenshots, and documentation. Give it the same options and seed, and it returns the same chronological readings every time.

npm install --save-dev @glucoseiq/testing

Generate a series

import { generateCGMSeries } from '@glucoseiq/testing'

const readings = generateCGMSeries({
  days: 14,
  seed: 7,
  start: '2025-01-01T00:00:00Z',
})

const firstReading = readings.at(0)
if (!firstReading) throw new Error('Expected generated readings')
firstReading
// {
//   value: 96,
//   unit: 'mg/dL',
//   timestamp: '2025-01-01T00:00:00.000Z'
// }

The generator combines a circadian baseline, three default meal responses, seeded noise, and optional nocturnal hypoglycemia. Values are clamped to 40–400 mg/dL before output conversion.

OptionDefaultPurpose
days1Number of days to generate
intervalMin5Minutes between readings
seed42Pseudorandom seed
start2024-01-01T00:00:00ZISO timestamp for the first reading
basal110Baseline in mg/dL
mealTimes07:00, 13:00, 19:00Meal times as minutes from midnight
mealAmplitude70Peak meal-excursion amplitude in mg/dL
noise8Noise amplitude in mg/dL
nocturnalHypoDays[]Zero-based day indexes with a 02:00–04:00 dip
unitmg/dLOutput as mg/dL or mmol/L

Test an analysis

Generated readings use the GlucoseReading contract from @glucoseiq/core, so they can go directly into reading-based analytics such as analyzeGlucose:

import { analyzeGlucose } from '@glucoseiq/core'
import { generateCGMSeries } from '@glucoseiq/testing'

const readings = generateCGMSeries({ days: 14, seed: 19 })
const report = analyzeGlucose(readings)

if (!report.valid || !report.agpProfile) {
  throw new Error('Expected a valid analysis with an AGP-style profile')
}
if (report.dataSufficiency.totalReadings !== 14 * 288) {
  throw new Error('Unexpected generated reading count')
}
if (report.agpProfile.bins.length !== 288) {
  throw new Error('Unexpected percentile-bin count')
}

Use a fixed start in tests that group by calendar date or local time. The default starts at midnight UTC.

Start from a scenario

The scenarios collection provides four named fixtures:

import { scenarios } from '@glucoseiq/testing'

const steady = scenarios.steadyDay()
const hypo = scenarios.hypoNight()
const volatile = scenarios.rollercoaster()
const withDropout = scenarios.gappyTrace()
ScenarioShape
steadyDay()A calm, mostly in-range day with smaller meals and less noise
hypoNight()A day with a 02:00–04:00 nocturnal dip
rollercoaster()Larger post-meal excursions and more noise
gappyTrace()A day with readings removed from 15:00 through 16:30 UTC

Scenarios return fresh arrays, so a test can safely derive a variant without changing the next fixture:

import { scenarios } from '@glucoseiq/testing'

const sparseHypo = scenarios.hypoNight().filter((_, index) => index % 2 === 0)

Produce mmol/L fixtures

import { generateCGMSeries } from '@glucoseiq/testing'

const readings = generateCGMSeries({
  unit: 'mmol/L',
  days: 2,
  seed: 23,
})

const firstReading = readings.at(0)
if (!firstReading) throw new Error('Expected generated readings')
firstReading.unit // 'mmol/L'

The underlying curve is generated in mg/dL and converted to one decimal place for mmol/L output.

Synthetic data is not clinical validation

These fixtures are designed for repeatable software testing and visual demos. They do not model a specific person, sensor, therapy, or clinical outcome, and they are not a substitute for validated reference datasets.

On this page