Code review comment: We should expect `assertFalse` here after solving the NPE issue caused by mocked `TaskSlotTable` mentioned above.

Code under review:
public void testConnectionTerminationAfterExternalRelease() throws IOException, InterruptedException, ExecutionException, TimeoutException {
		final JobMasterId jobMasterId = JobMasterId.generate();

		final LibraryCacheManager libraryCacheManager = mock(LibraryCacheManager.class);
		when(libraryCacheManager.getClassLoader(any(JobID.class))).thenReturn(ClassLoader.getSystemClassLoader());

		final JobMasterGateway jobMasterGateway = mock(JobMasterGateway.class);
		when(jobMasterGateway.getFencingToken()).thenReturn(jobMasterId);

		final TaskManagerActions taskManagerActions = new NoOpTaskManagerActions();
		final JobManagerConnection jobManagerConnection = new JobManagerConnection(
			jobId,
			ResourceID.generate(),
			jobMasterGateway,
			taskManagerActions,
			mock(CheckpointResponder.class),
			new TestGlobalAggregateManager(),
			libraryCacheManager,
			new NoOpResultPartitionConsumableNotifier(),
			mock(PartitionProducerStateChecker.class));

		final JobManagerTable jobManagerTable = new JobManagerTable();
		jobManagerTable.put(jobId, jobManagerConnection);

		final AtomicBoolean hasPartitionsOccupyingLocalResources = new AtomicBoolean(true);
		final TestJobAwareShuffleEnvironment jobAwareShuffleEnvironment = new TestJobAwareShuffleEnvironment(jobId -> hasPartitionsOccupyingLocalResources.get());

		final TaskManagerServices taskManagerServices = new TaskManagerServicesBuilder()
			.setJobManagerTable(jobManagerTable)
			.setShuffleEnvironment(jobAwareShuffleEnvironment)
			.build();

		final TestingTaskExecutor taskManager = createTestingTaskExecutor(taskManagerServices, new HeartbeatServices(Long.MAX_VALUE, Long.MAX_VALUE));

		try {
			taskManager.start();
			taskManager.waitUntilStarted();

			assertTrue(taskManagerServices.getJobManagerTable().contains(jobId));

			taskManager.releasePartitions(jobId, Collections.singletonList(new ResultPartitionID()));
			
			assertTrue(taskManagerServices.getJobManagerTable().contains(jobId));

			hasPartitionsOccupyingLocalResources.set(false);

			taskManager.releasePartitions(jobId, Collections.singletonList(new ResultPartitionID()));
			assertTrue(taskManagerServices.getJobManagerTable().contains(jobId));
		} finally {
			RpcUtils.terminateRpcEndpoint(taskManager, timeout);
		}
	}

Line commented on: assertTrue(taskManagerServices.getJobManagerTable().contains(jobId));

Revised code:
public void testConnectionTerminationAfterExternalRelease() throws Exception {
		final CompletableFuture<Void> disconnectFuture = new CompletableFuture<>();
		final JobMasterGateway jobMasterGateway = new TestingJobMasterGatewayBuilder()
			.setDisconnectTaskManagerFunction(resourceID -> {
				disconnectFuture.complete(null);
				return CompletableFuture.completedFuture(Acknowledge.get());
			}).build();

		final JobManagerConnection jobManagerConnection = TaskSubmissionTestEnvironment.createJobManagerConnection(
			jobId, jobMasterGateway, RPC, new NoOpTaskManagerActions(), timeout);

		final JobManagerTable jobManagerTable = new JobManagerTable();
		jobManagerTable.put(jobId, jobManagerConnection);

		final TestingShuffleEnvironment shuffleEnvironment = new TestingShuffleEnvironment();

		final TaskManagerServices taskManagerServices = new TaskManagerServicesBuilder()
			.setJobManagerTable(jobManagerTable)
			.setShuffleEnvironment(shuffleEnvironment)
			.setTaskSlotTable(createTaskSlotTable())
			.build();

		final PartitionTable partitionTable = new PartitionTable();
		final ResultPartitionID resultPartitionId = new ResultPartitionID();

		final TestingTaskExecutor taskExecutor = createTestingTaskExecutor(taskManagerServices, partitionTable);

		try {
			taskExecutor.start();
			taskExecutor.waitUntilStarted();

			final TaskExecutorGateway taskExecutorGateway = taskExecutor.getSelfGateway(TaskExecutorGateway.class);

			
			runInTaskExecutorThreadAndWait(taskExecutor, () -> assertTrue(jobManagerTable.contains(jobId)));

			runInTaskExecutorThreadAndWait(taskExecutor, () -> partitionTable.startTrackingPartitions(jobId, Collections.singletonList(resultPartitionId)));

			final CompletableFuture<Collection<ResultPartitionID>> firstReleasePartitionsCallFuture = new CompletableFuture<>();
			runInTaskExecutorThreadAndWait(taskExecutor, () -> shuffleEnvironment.releasePartitionsLocallyFuture = firstReleasePartitionsCallFuture);

			taskExecutorGateway.releasePartitions(jobId, Collections.singletonList(new ResultPartitionID()));

			
			
			firstReleasePartitionsCallFuture.get();

			
			
			runInTaskExecutorThreadAndWait(taskExecutor, () -> assertTrue(jobManagerTable.contains(jobId)));

			final CompletableFuture<Collection<ResultPartitionID>> secondReleasePartitionsCallFuture = new CompletableFuture<>();
			runInTaskExecutorThreadAndWait(taskExecutor, () -> shuffleEnvironment.releasePartitionsLocallyFuture = secondReleasePartitionsCallFuture);

			
			taskExecutorGateway.releasePartitions(jobId, Collections.singletonList(resultPartitionId));

			disconnectFuture.get();
		} finally {
			RpcUtils.terminateRpcEndpoint(taskExecutor, timeout);
		}
	}