Playframework 2.6与Macwire编译时依赖注入配置与测试咨询
Hey there! Let's walk through getting your Play 2.6 + MacWire compile-time DI setup working properly, along with testing strategies tailored to your project structure.
1.1 Finish the DefyProductsComponents Class
Your existing component class needs to extend the right base components and wire up all dependencies using MacWire. Here's the full implementation:
package common import com.softwaremill.macwire.wire import controllers.{Assets, AssetsComponents, HomeController} import play.api.ApplicationLoader.Context import play.api.BuiltInComponentsFromContext import play.api.routing.Router import play.filters.HttpFiltersComponents import router.Routes import scala.concurrent.ExecutionContext class DefyProductsComponents(context: Context) extends BuiltInComponentsFromContext(context) with HttpFiltersComponents // Includes default HTTP filters with AssetsComponents { // Provides Assets controller dependencies // Explicitly provide ExecutionContext (reused from Play's default) implicit override lazy val executionContext: ExecutionContext = actorSystem.dispatcher // Wire up your custom controllers lazy val homeController: HomeController = wire[HomeController] // Build the router using the wired controllers override lazy val router: Router = { val prefix = "/" wire[Routes].setPrefix(prefix) } }
Key Notes:
- We extend
BuiltInComponentsFromContextto get core Play components (likeactorSystem,configuration), plusHttpFiltersComponentsandAssetsComponentsto cover filter and asset handling. wire[HomeController]tells MacWire to automatically resolve all constructor dependencies for your controller (make sure yourHomeController's constructor accepts dependencies that are available in the components class).- The router is wired using Play's generated
Routesclass, with the default root prefix set.
1.2 Implement the DefyProductsLoader
Next, complete your application loader to use the components class:
package common import play.api.ApplicationLoader import play.api.ApplicationLoader.Context class DefyProductsLoader extends ApplicationLoader { override def load(context: Context) = { new DefyProductsComponents(context).application } }
1.3 Configure Play to Use Your Custom Loader
Add this line to your conf/application.conf to tell Play to use your loader instead of the default Guice one:
play.application.loader = common.DefyProductsLoader
Testing with compile-time DI avoids runtime injection surprises—you can either test individual components directly or spin up a full application stack with your components.
2.1 Component-Level Unit Tests
For testing controllers or services in isolation, wire them directly with mocked dependencies. Let's say your HomeController depends on a ProductService:
Example HomeController (for reference):
package controllers import play.api.mvc._ import common.ProductService class HomeController(val controllerComponents: ControllerComponents, productService: ProductService) extends BaseController { // Example action def index() = Action { Ok("Hello from HomeController") } }
Unit Test for HomeController:
package controllers import org.scalatestplus.play.PlaySpec import play.api.test.FakeRequest import play.api.test.Helpers._ import com.softwaremill.macwire.wire import common.MockProductService // Your custom mocked service class HomeControllerSpec extends PlaySpec { // Mock dependencies private val mockProductService = new MockProductService() private val controllerComponents = stubControllerComponents() // Play's pre-built stub for controller dependencies // Wire the controller with mocked dependencies private val homeController = wire[HomeController] "HomeController" should { "return 200 for GET /" in { val result = homeController.index().apply(FakeRequest(GET, "/")) status(result) mustBe OK } } }
2.2 Full Application Integration Tests
If you need to test the full application stack (routes, filters, etc.), create your components instance and start the app using Play's test helpers:
package integration import org.scalatestplus.play.{OneAppPerTest, PlaySpec} import play.api.Application import play.api.test.FakeRequest import play.api.test.Helpers._ import common.DefyProductsComponents import play.api.ApplicationLoader.Context import play.api.test.DefaultTestContext class HomeControllerIntegrationSpec extends PlaySpec with OneAppPerTest { // Override app creation to use our components instead of Guice override def app: Application = { val testContext = Context(DefaultTestContext()) new DefyProductsComponents(testContext).application } "HomeController integration test" should { "render the index page successfully" in { val request = FakeRequest(GET, "/") val homeResponse = route(app, request).get status(homeResponse) mustBe OK contentType(homeResponse) mustBe Some("text/html") } } }
Key Testing Tips:
- Use Play's
stubControllerComponents()for mocking controller-related dependencies in lightweight unit tests. - For integration tests, use
DefaultTestContext()to create a test-friendly application context that avoids production configuration. - MacWire will throw compile errors if dependencies are missing—this is a feature! It catches DI issues early, before runtime.
内容的提问来源于stack exchange,提问作者agusgambina

