Code review comment: Yes, but afaik, it would require quite a bit of refactoring, which I'd do in another PR.

Code under review:
public void testClosingWithBlockedEmitter() throws Exception {
		final Object lock = new Object();

		ArgumentCaptor<Throwable> failureReason = ArgumentCaptor.forClass(Throwable.class);

		MockEnvironment environment = createMockEnvironment();

		StreamTask<?, ?> containingTask = mock(StreamTask.class);
		TaskMailboxImpl mailbox = new TaskMailboxImpl();
		when(containingTask.getEnvironment()).thenReturn(environment);
		when(containingTask.getCheckpointLock()).thenReturn(lock);
		when(containingTask.getProcessingTimeService()).thenReturn(new TestProcessingTimeService());
		when(containingTask.getTaskMailboxExecutor(any())).thenReturn(new MailboxExecutorImpl(mailbox));

		StreamConfig streamConfig = new MockStreamConfig();
		streamConfig.setTypeSerializerIn1(IntSerializer.INSTANCE);

		final OneShotLatch closingLatch = new OneShotLatch();
		final OneShotLatch outputLatch = new OneShotLatch();

		Output<StreamRecord<Integer>> output = mock(Output.class);
		doAnswer(new Answer() {
			@Override
			public Object answer(InvocationOnMock invocation) throws Throwable {
				assertTrue("Output should happen under the checkpoint lock.", Thread.currentThread().holdsLock(lock));

				outputLatch.trigger();

				
				while (!closingLatch.isTriggered()) {
					lock.wait();
				}

				return null;
			}
		}).when(output).collect(any(StreamRecord.class));

		AsyncWaitOperator<Integer, Integer> operator = new TestAsyncWaitOperator<>(
			new MyAsyncFunction(),
			1000L,
			1,
			AsyncDataStream.OutputMode.ORDERED,
			closingLatch);

		operator.setup(
			containingTask,
			streamConfig,
			output);

		mailbox.open();
		operator.open();

		synchronized (lock) {
			operator.processElement(new StreamRecord<>(42));
		}

		outputLatch.await();

		synchronized (lock) {
			operator.close();
		}
	}

Line commented on: when(containingTask.getTaskMailboxExecutor(any())).thenReturn(new MailboxExecutorImpl(mailbox));

Revised code:
public void testClosingWithBlockedEmitter() throws Exception {

		JobVertex chainedVertex = createChainedVertex(new MyAsyncFunction(), new EmitterBlockingFunction());

		final OneInputStreamTaskTestHarness<Integer, Integer> testHarness = new OneInputStreamTaskTestHarness<>(
				OneInputStreamTask::new,
				1, 1,
				BasicTypeInfo.INT_TYPE_INFO, BasicTypeInfo.INT_TYPE_INFO);
		testHarness.setupOutputForSingletonOperatorChain();

		testHarness.taskConfig = chainedVertex.getConfiguration();

		final StreamConfig streamConfig = testHarness.getStreamConfig();
		final StreamConfig operatorChainStreamConfig = new StreamConfig(chainedVertex.getConfiguration());
		final AsyncWaitOperator<Integer, Integer> headOperator =
				operatorChainStreamConfig.getStreamOperator(AsyncWaitOperatorTest.class.getClassLoader());
		streamConfig.setStreamOperator(headOperator);

		testHarness.invoke();
		testHarness.waitForTaskRunning();
		Object checkpointLock = testHarness.getTask().getCheckpointLock();
		EmitterBlockingFunction.setLock(checkpointLock);

		testHarness.processElement(new StreamRecord<>(42, 1L));

		EmitterBlockingFunction.outputLatch.await();
		testHarness.endInput();
		EmitterBlockingFunction.closingLatch.trigger();
		testHarness.waitForTaskCompletion();

		assertEquals(emptyList(), new ArrayList<>(testHarness.getOutput()));
	}