In a component test of a parent, how do you trigger an event a child component emits, and what should you assert?
answer
- the output channel is not the host tree
- real child or stub child
- trigger the output, not a host event
- assert the parent's rendered response
- the payload's shape is where parents break
basics
~20 sGo through the framework's own output channel: emit from a stub child, or drive the real child's control so its binding emits. Then assert on what the parent rendered from the payload, not on the emitter being called.
solid answer
~50 sA child's output is not a host event on the child's root node, so dispatching a same-named host event there usually triggers nothing. Two seams work. Drive the **real** child through its own bound control and let its binding emit upward: that covers the whole path, but couples the parent's test to the child's markup. Or render a **stub** child whose only job is to emit the output with a chosen payload: fast and decoupled, at the price of not proving the real child ever emits it. In both cases assert on the parent's own behaviour - the output it rendered, the request it made, the payload it forwarded on - and on the **shape** of what it received, since a parent that mis-reads the payload (taking the whole event object where a value was emitted, or reading the wrong field) still passes a test that only checks the emitter was called.
go deeper
Know that a child's announced event travels on the framework's own channel, so you trigger it by emitting it, not by dispatching a host event on the child's element. Then check what the parent rendered.
Explain the two seams - real child driven through its own control versus a stub child that emits - and what each covers. Mention that the emit is a state write, so the assertion comes after the settle.
Show the failure the weak version misses: a parent that mis-reads the payload. Use the shape the child really emits, assert on the parent's rendered response, and keep a contract test on the child so a stub cannot drift.
Set the boundary policy: which children are stubbed by default, who owns the contract test that keeps the stub honest, and how one broader test guards against an upstream child changing its output without anyone's suite going red.
## A component output is not a host event When a parent listens for something a child announces, the wiring is the framework's own: the child is handed a callback, or the framework registers the parent's handler against the child's declared output and invokes it when the child emits. That channel lives above the host tree. Dispatching a host event of the same name on the child's root node therefore does not reach the parent's handler - there is no host listener there to catch it - unless the child was deliberately built to re-dispatch its output as a host event as well. This is the first thing to get right in a parent's test, because the symptom is a silent nothing: the dispatch succeeds, no handler runs, and the assertion fails with no clue about which layer swallowed it. ## The two seams, and what each buys | | Real child, driven through its own control | Stub child that emits | |---|---|---| | What the test does | put a value on the child's control, dispatch its binding's event | call the output the stub was handed, with a chosen payload | | What it proves | the whole path: child binding, emit, parent handling | the parent's handling, given a payload | | What it misses | little, but it is slow and broad | that the real child ever emits, and with that shape | | Breaks when | the child's internal markup changes | the child's output contract changes silently | | Best for | a small, stable child the parent is meaningless without | a heavy child, or many payload cases | The honest way to use the second column is to pair it with a **contract test on the child itself**: the child's own test proves it emits the output with that payload when driven, and the parent's test proves what it does with such a payload. Together they cover the path the first column covers in one go. Without the child's test, a stub child lets the two halves drift until nothing emits and every test is green. ## What to assert in the parent Assert on the parent's observable response, not on the mechanics of the call: - the parent's rendered output changed in the way the payload implies - the selected row highlighted, the summary recalculated, the panel opened on that item; - the parent's own upward output carries the right value, when it forwards a transformed payload to its own parent; - a collaborator the test supplied received the expected argument, when handling means issuing a command; - the payload's **shape** was read correctly: a parent that expects a plain value but receives an object, or reads a field the child does not emit, fails here and nowhere else. What not to assert: that the stub's emitter was called. The test itself called it, so that assertion is circular in exactly the way reading back a value you wrote is. ## Payload shape is where parents actually break The most common real defect at this seam is not a missing handler but a mis-read payload. A child emits a value; the parent's handler is written as though it received a host event and reaches for a property that is not there. Or the child emits an object with the item and its index, and the parent reads only the index and then indexes a filtered array. Tests that assert "the handler ran" never see either bug. Tests that assert on rendered output from a realistic payload see both immediately, which is why the payload used in the test should be the shape the child really emits - copied from the child's contract, not invented. ## Ordering and settling An emit is a state write in the parent, so the same discipline applies as for a host-event binding: after triggering the output, let the framework settle before asserting on rendered output. Under a runtime that re-runs the component function the parent re-derives its subtree; under a fine-grained runtime only the subscribers of the changed value are notified; under a compile-time runtime the generated invalidation runs. In all three, the assertion belongs after the settle, and a missing settle looks confusingly like a handler that never ran. ## Choosing between the seams in practice 1. If the child is small, stable, and the parent's behaviour is meaningless without it, drive the real child. The extra coverage is nearly free and the coupling is honest. 2. If the child is heavy, owns its own suite, or the parent needs a dozen payload cases, stub it and trigger the output directly - then check the child's suite really pins the contract. 3. If the child comes from outside the codebase, prefer the stub and keep one broader test that exercises the real thing, so an upstream contract change is not invisible. 4. Whichever seam you pick, name it in the test so the next reader knows what the green result covers.
- Why is asserting that a stub child's emitter was called a weak test?Because the test called it. The assertion confirms the test's own action, not the parent's handling, and it stays green when the parent mis-reads the payload or renders nothing. Assert on what the parent produced from the payload instead.
- What breaks if a parent's test always stubs the child?The contract can drift: the real child may stop emitting, rename the output, or change the payload's shape, and every stubbed test stays green. That is why the child needs its own test proving it emits that payload when driven, or one broader test that renders both together.
- How do you choose the payload the test emits?Copy the shape from the child's contract rather than inventing one. An invented payload can be richer or flatter than reality, which either hides a parent that mis-reads a field or fails for a reason the product never encounters.
saying these in an interview costs you the question
- Dispatches a host event on the child's node to trigger its output
- Asserts only that the child's emitter was called
- Stubs every child and never tests the child's own output contract
- Invents a payload shape the real child never emits
- Asserts on rendered output before the framework settles
- Thinks a parent's handler receives a host event object by default