Photo by victor estrada diazLeverage Spring’s dependency injection for UI automationMartin SchneiderBlockedUnblockFollowFollowingMay 22Spring is a widely used and established Java application framework.
The Page Object Pattern is the de-facto standard for implementing UI tests in an object-oriented manner.
In this article, we will see how we can combine the two to simplify writing these tests.
We will use an Appium test (executed with JUnit) as an example.
For Selenium, the code remains pretty much the same.
For other test automation tools, the concept applies too.
Test without page objectsFirst, let’s have a look at a test that doesn’t use the Page object pattern.
We use a simple login test for the Carousell app.
The full source code is available on GitHub.
Carousell login flowAs you can see, all locators are packed into the test class itself.
If we want to re-use them across multiple tests we need to duplicate them (or worse, reference them across different test classes).
It’s evident that this would become a maintenance nightmare for larger test suites.
Introducing page objectsMoving the locators to separate Java object solves this problem.
It encapsulates the technical details from the test and makes its (functional) steps clearer.
The page objects responsibility is to model pages (or screens) of the application.
The tests responsibility is to interact with those pages.
You can easily re-use page objects across multiple tests.
If the layout of your application changes, ideally, you only need to touch the page objects.
If you want to modify or add tests, your existing page objects will already be a good starting point.
Below you can see the three screens in our test and the corresponding page object classes.
Welcome pageLogin pageHome pageSo far so goodOne challenge with using page objects in Java is the question of how to create instances of them.
In the example above, we use a constructor call:new WelcomePage(driver);Creation of all subsequent pages happens in the page object classes.
Each method returns an instance of the page object representing the screen the application will be on after its execution.
For example, the login method on the LoginPage will return an instance of WelcomePage (so the test doesn't need to care about resolving it).
I consider this good practice because it allows method chaining and thus a fluent coding style.
The alternative to it would require to instantiate all page objects in the test:Regardless of whether in the test class or the page objects we need to call the constructor for all page objects in our code at some point.
In the Selenium/Appium world, page objects require an instance of the WebDriver.
One common practice is to pass it to the constructor or use some form of a page object factory.
All of these are implementation details I don’t want to bother with when writing tests.
Let’s invest a little in our framework to hide this complexity from the test itself.
The following code snippet shows how our test and the test objects (i.
all relevant test code) will look like after we’re done:What’s going on behind the scenes?First, let’s include two Spring libraries into our project.
For Maven, add this to your pom file:<dependency> <groupId>org.
springframework</groupId> <artifactId>spring-core</artifactId> <version>5.
springframework</groupId> <artifactId>spring-test</artifactId> <version>5.
RELEASE</version></dependency>By introducing Spring, we turn the page objects, test class and the WebDriver into Spring beans and let the Spring IoC container manage them.
The Java configuration is quite simple.
We use @ComponentScan to specify the packages Spring will scan for beans.
It will find all classes annotated with @Component and use the default (in our case empty) constructors to instantiate them.
For the WebDriver, we create a method to describe how to construct it and annotate it with @Bean.
Spring will call this method to construct a WebDriver instance.
To make use of Spring during our test execution, we need to annotate the test class with@SpringJUnitConfig(SpringConfig.
class)All Spring beans can be injected into other beans using the @Autowired annotation.
There are multiple ways to achieve this, the simplest is using private fields, for example:@Autowiredprivate LoginPage login;@Autowiredprivate WelcomePage welcome;@Autowiredprivate WebDriver driver;That’s it!.Spring will take care of the rest.
No more need to care about constructors or how to inject the WebDriver into 100 different page objects.
This is what’s sometimes referred to as the Hollywood Principle: don’t call us, we (Spring) will call you.
All Spring beans, by default, are singletons which means they are created once during the start-up of the Spring context and then re-used whenever they’re needed.
Page objects should be stateless, so this fits well.
ConclusionThe use of Spring beans is, of course, not limited to page objects (or the WebDriver).
It’s an advantageous way to inject other services (test data handling, API calls etc.
) into your test code as well.
On top of that, you can utilise Spring for a thousand other use-cases besides IoC and DI which is another reason why, despite a slightly increased footprint from its libraries, I prefer Spring to a more light-weight or even homegrown dependency injection approach.
A good example of a test-related use-case is an easy-to-use retry-mechanism which I’m going to explore in a future article.
ResourcesSpring frameworkInversion of Control Containers and the Dependency Injection pattern by Martin FowlerMy colleague Abhijeet also gave a talk covering the benefits of dependency injection in test frameworks.
.. More details