A lot of logic, such as pausing the game, expects a player to exist in the game instance, with a valid controller and some other things. The following sets it up so it should work.

auto* GameInstance = NewObject<UGameInstance>(GEngine);
UWorld* World = UWorld::CreateWorld(EWorldType::Game, false);
World->SetGameInstance(GameInstance);
//This trigges the creation of a game mode which is required for some things
World->SetGameMode(FURL());
 
//Set up the world context which is sometimes required to find the player
FWorldContext& Context = GEngine->CreateNewWorldContext(EWorldType::Game);
Context.SetCurrentWorld(World);
Context.GameViewport = NewObject<UGameViewportClient>(GEngine);
Context.OwningGameInstance = World->GetGameInstance();
World->InitializeActorsForPlay(FURL());
 
//Setup the viewport which is required for some things as well
TSharedRef<SOverlay> DudOverlay = SNew(SOverlay);
Context.GameViewport->SetViewportOverlayWidget(nullptr, DudOverlay);
 
//Spawn the player controller and set up things it often requires
auto* PlayerController = World->SpawnActor<APlayerController>();
auto* LP = NewObject<ULocalPlayer>(GEngine, GEngine->LocalPlayerClass);
LP->SwitchController(PlayerController);
PlayerController->Player = LP;
GameInstance->AddLocalPlayer(LP, FPlatformUserId());
auto* Pawn = World->SpawnActor<APawn>();
PlayerController->Possess(Pawn);

This setup should also allow for UUserWidgets to be addable to the viewport.