Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question] Global start/bootstrap logic before all tests #1983

Open
lukoyanov opened this issue Jul 6, 2023 · 2 comments
Open

[Question] Global start/bootstrap logic before all tests #1983

lukoyanov opened this issue Jul 6, 2023 · 2 comments

Comments

@lukoyanov
Copy link

Hi, Team!

First of all, thank you all for the awesome Scala FP DI Framework. We use it a lot.

Today, I have one question regarding the best way of implementing global bootstrap logic to be run before all tests.

Currently, we do it like this

// Some base class for all tests
class BaseZioItSpec
    extends Spec3[ZIO] 
    with ... {

  override def config: TestConfig = 
      TestConfig(
          pluginConfig = PluginConfig.const(DefaultPlugin),
          moduleOverrides = new ModuleDef {
             ...
             make[Unit].named("global-start").fromEffect(fixtureBootstrap _)
          },
          forcedRoots = Set(
            ...
            DIKey.get[Unit].named("global-start")
          ),
          memoizationRoots = Set(
            ...
            DIKey.get[Unit].named("global-start"),
          )
     )

   ...

  /** Creates test data */
  def fixtureBootstrap(
      xa: Transactor[Task],
      someRepository: SomeRepository,
      userService: UserService
  ): UIO[Unit] = {
        for {
          _ <- UIO(println("Bootstrapping data for tests"))
          _ <- userService.createUser(NewUser("test-1"...))
        ...
        } yield ()
  }

}

The problem:
Although we have our fixtureBootstrap run once before all tests, we have a problem with the userService and its dependencies. The arguments of fixtureBootstrap and ALL nested dependencies (there could be quite a lot of them) of the components in the arguments are created only once and shared across all tests.

This is undesirable sometimes.

We potentially can only inject Transactor[Task] into fixtureBootstrap only and duplicate some logic from other components like userService to "unblock it" and make it re-creatable on every test, but there may be another way of doing it.

Please kindly advise how we can define a global bootstrap logic without pinning some portion of the instances in the DI graph.

Thank you.

@neko-kai
Copy link
Member

neko-kai commented Jul 6, 2023

@lukoyanov

We potentially can only inject Transactor[Task] into fixtureBootstrap only and duplicate some logic from other components like userService to "unblock it" and make it re-creatable on every test, but there may be another way of doing it.

This is what we do in general.

Alternatively you can make named copies of the graphs that you use in the startup task. Though that may be bothersome to do - you'll need to assign names to all the components AND to their dependencies - to prevent dependencies from being memoized:

val constructorOfUserService = AnyConstructor[UserService]
val constructorOfSomeRepository = AnyConstructor[SomeRepository]

def makeAllDependenciesNamed[A](functoid: Functoid[A], name: Identifier): Functoid[A] = {
    val newProvider = functoid.get.replaceKeys {
      case DIKey.TypeKey(tpe, m) =>
        DIKey.IdKey(paramTpe, name.id, m)(name.idContract)
      case k => k
    }
    Functoid(newProvider)
}

val globalModule = new ModuleDef {
  make[UserService].named("global").from(makeAllDependenciesNamed(constructorOfUserService, "global"))
  make[SomeRepository].named("global").from(makeAllDependenciesNamed(constructorOfSomeRepository, "global"))
  
  ... make[SomeRepositoryDep].named("global").from(...) ...
}

There could be a better solution for this in the future with private bindings

@lukoyanov
Copy link
Author

Thank you for the details and provided snippet @neko-kai!
I'll give it a try with makeAllDependenciesNamed, and will wait for the private bindings to be available in future releases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants