Environment variables

There are multiple mocks working together in this test suite, so I make sure to establish them (and their cleanup) accordingly:
beforeAll(() => {
  vi.spyOn(console, 'log').mockImplementation(() => {})
})

afterEach(() => {
  vi.resetAllMocks()
  vi.unstubAllEnvs()
})

afterAll(() => {
  vi.restoreAllMocks()
})
I'm using vi.spyOn(console, 'log') to be able to assert on console.log calls in tests, but more importantly I'm clearing any environment variable mocks by adding vi.unstubAllEnvs() to the afterEach() hook.
I'm using the afterEach() hook here so that individual test cases can have different values for the same environment variables.
In the first test, the intention is to make sure that our Emitter class prints messages to the console when process.env.NODE_ENV is "development". I will mock the value of that environment variable using vi.stubEnv() as the first thing in the test:
test('logs debugging messages when run in development', () => {
  vi.stubEnv('NODE_ENV', 'development')
Note that it's possible (and sometimes even beneficial) to have a combination of both global (the before* and after* hooks) and local test setups, like we have here with the vi.stubEnv() function inside this test.
The vi.stubEnv() utility accepts two arguments:
  • A variable name to stub (e.g. NODE_ENV);
  • A mock value.
You can imagine the vi.stubEnv function doing the following:
function stubEnv(variableName, value) {
  process.env[variableName] = value
}
However, unlike this simplified example, the actual function also allows you to restore the mocked variables without having to manually keep track of their original values.
With that, this test case is configured, and the only thing remaining is to act and assert:
test('logs debugging messages when run in development', () => {
  vi.stubEnv('NODE_ENV', 'development')

  const emitter = new Emitter<{ hello: [firstName: string] }>()
  const listener = vi.fn()

  emitter.on('hello', listener)
  expect(console.log).toHaveBeenCalledWith(
    `adding listener for "hello" event...`,
  )

  emitter.emit('hello', 'John')
  expect(console.log).toHaveBeenCalledWith(
    `emitting listeners for "hello" event (1)`,
  )
})
Here, I'm adding new event listeners and emitting events, following those actions with assertions to make sure console.log is called appropriately in both cases.
The second test case will be quite similar:
  1. Mock the value of process.env.NODE_ENV;
  2. Add listeners/emit events;
  3. Assert that console.log has NOT been called.
test('does not log debugging messages in production', () => {
  vi.stubEnv('NODE_ENV', 'production')

  const emitter = new Emitter<{ hello: [firstName: string] }>()
  const listener = vi.fn()

  emitter.on('hello', listener)
  expect(console.log).not.toHaveBeenCalled()

  emitter.emit('hello', 'John')
  expect(console.log).not.toHaveBeenCalled()
})
Although this test case will pass even if you omit the vi.stubEnv() setup, that isn't correct. It passes because Vitest sets process.env.NODE_ENV to "test" by default, but your intention is not to test how Emitter behaves in test. It is to check how it behaves in production.