Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Serverless Programming Cookbook

You're reading from   Serverless Programming Cookbook Practical solutions to building serverless applications using Java and AWS

Arrow left icon
Product type Paperback
Published in Jan 2019
Publisher Packt
ISBN-13 9781788623797
Length 490 pages
Edition 1st Edition
Languages
Tools
Concepts
Arrow right icon
Author (1):
Arrow left icon
Heartin Kanikathottu Heartin Kanikathottu
Author Profile Icon Heartin Kanikathottu
Heartin Kanikathottu
Arrow right icon
View More author details
Toc

Table of Contents (12) Chapters Close

Preface 1. Getting Started with Serverless Computing on AWS FREE CHAPTER 2. Building Serverless REST APIs with API Gateway 3. Data Storage with Amazon DynamoDB 4. Application Security with Amazon Cognito 5. Web Hosting with S3, Route53, and CloudFront 6. Messaging and Notifications with SQS and SNS 7. Redshift, Amazon ML, and Alexa Skills 8. Monitoring and Alerting with Amazon CloudWatch 9. Serverless Programming Practices and Patterns 10. Other Cloud Providers 11. Other Books You May Enjoy

Dev Practices – dependency injection and unit testing

In this recipe, I will implement some of the common dev practices for creating Lambdas, such as using lightweight frameworks for dependency injection and writing unit tests for your code.

For dependency injection, we will use Guice, which is one of the dependency injection (IoC) frameworks suggested by AWS at https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html. For unit testing, we will use JUnit and Mockito libraries.

Getting ready

You need an active AWS account, and read and follow the Getting started section of the recipes, Your first AWS Lambda and Your first Lambda with AWS CLI to set up Java, Maven, the parent project, serverless-cookbook-parent-aws-java, and AWS CLI, and other code usage guidelines.

This recipe also assumes you are familiar with general software development concepts and practices such as dependency injection, unit testing, and coding to interfaces. Familiarity with libraries such as JUnit and Mockito will be good to have.

Code refactoring

We will be improving the code we created in the Using AWS SDK, Amazon CloudWatch and AWS CLI with Lambda recipe. Before doing Dependency Injection, you need to refactor your code to follow the principle of programming to interfaces.

Refactor the service class into an interface and its implementation. I will also add lombok's @AllArgsConstructor annotation to generate an all args constructor, which will be used during unit testing to inject the mock object.

  1. We will first create an interface IAMService:
/**
* Interface for IAM operations.
*/
public interface IAMService {

We will define the corresponding implementation as IAMServiceImpl:

/**
* Implementation of {@link IAMService}.
*/
@AllArgsConstructor
public class IAMServiceImpl implements IAMService {
  1. Extract the methods as well, and then replace the usage of the implementation with an interface:
private IAMService service;

public MyLambdaHandler() {
service = new IAMServiceImpl();
}
Most IDEs will provide refactoring support to extract an interface from an implementation. IDEs will also help you in replacing the usages of your implementation with interface wherever possible.

How to do it...

Let us do dependency injection with Guice, which is a lightweight framework suggested by AWS.

  1. Add Maven dependency for Guice:
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.2.0</version>
</dependency>
  1. Create the Guice configuration class to bind interfaces to implementation:
public class ApplicationModule extends AbstractModule {
protected final void configure() {
bind(IAMService.class).to(IAMServiceImpl.class);
}
}
  1. Configure the handler class for using Guice:
public final class MyLambdaHandler implements RequestHandler<IAMOperationRequest, IAMOperationResponse> {

private static final Injector INJECTOR =
Guice.createInjector(new ApplicationModule());

private IAMService service;

public MyLambdaHandler() {
INJECTOR.injectMembers(this);
Objects.requireNonNull(service);
}

@Inject
public void setService(final IAMService service) {
this.service = service;
}

I created a static Injector class and initialized it with our Guice configuration class. I added a default constructor to add this class to be injected by Guice. Objects.requireNonNull verifies if the implementation was injected successfully. I annotated it with Java's @Inject annotation for Guice to inject dependency.

Let us write unit tests for our code.

  1. Add Maven dependency for JUnit and Mockito:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.21.0</version>
<scope>test</scope>
</dependency>
  1. Create a simple test class for the handler that checks if the service implementation is injected:
package tech.heartin.books.serverlesscookbook;

import org.junit.Test;

public class MyLambdaHandlerTest {
@Test
public void testDependencies() throws Exception {
MyLambdaHandler testHandler = new MyLambdaHandler();
}
}
  1. Create a test class for the service class that uses Mockito to mock AWS calls:
@RunWith(MockitoJUnitRunner.class)
public class IAMServiceImplTest {

@Mock
private AmazonIdentityManagement iamClient;

private IAMService service;

@Before
public void setUp() {
service = new IAMServiceImpl(iamClient);
Objects.requireNonNull(service);
}
// Actual tests not shown here
}
  1. Add the test method for create user:
@Test
public void testCreateUser() {
IAMOperationResponse expectedResponse = new IAMOperationResponse(
"Created user test_user", null);
when(iamClient.createUser(any()))
.thenReturn(new CreateUserResult()
.withUser(new User().withUserName("test_user")));
IAMOperationResponse actualResponse
= service.createUser("test_user");
Assert.assertEquals(expectedResponse, actualResponse);
}
  1. Add the test method to check user:
@Test
public void testCheckUser() {
IAMOperationResponse expectedResponse = new IAMOperationResponse(
"User test_user exist", null);
when(iamClient.listUsers(any()))
.thenReturn(getListUsersResult());
IAMOperationResponse actualResponse
= service.checkUser("test_user");
Assert.assertEquals(expectedResponse, actualResponse);
}

private ListUsersResult getListUsersResult() {
ListUsersResult result = new ListUsersResult();
result.getUsers().add(new User().withUserName("test_user"));
  1. Add the test method to delete user:
@Test
public void testDeleteUser() {
IAMOperationResponse expectedResponse = new IAMOperationResponse(
"Deleted user test_user", null);
when(iamClient.deleteUser(any()))
.thenReturn(new DeleteUserResult());
IAMOperationResponse actualResponse
= service.deleteUser("test_user");
Assert.assertEquals(expectedResponse, actualResponse);
}
  1. To Package, deploy, and verify, follow the Using AWS SDK, Amazon CloudFormation and AWS CLI with Lambda recipe, and package, deploy, and verify by invoking the Lambda.
In real-world projects, you may follow the Test Driven Development (TDD) principle and write tests before actual code.

How it works...

We added a lightweight dependency injection framework, Guice, and modified code to incorporate it. We also used JUnit and Mockito to do unit testing of the code. Going deep into the working of Guice, JUnit, or Mockito is outside the scope of this book. But, you may ask any questions on the open source repository for the project (given in the introduction in Chapter 1, Getting Started with Serverless Computing on AWS).

There's more...

You may also use Dagger instead of Guice for dependency injection. Dagger is also a recommended framework from AWS for lightweight dependency injection. You can technically use Spring for dependency injection, but it is not recommended because of its bigger size.

You may use TestNG instead of JUnit for unit testing. TestNG provides additional features such as DataProviders. DataProviders allow you to supply an array with all possible inputs and their expected values for a single test method. With JUnit, you will have to write a test method per input combination. You may also use Hamcrest to create more flexible expressions in tests.

See also

  • You may refer to other books at PacktPub to become familiar with the dependency injection and testing frameworks.
You have been reading a chapter from
Serverless Programming Cookbook
Published in: Jan 2019
Publisher: Packt
ISBN-13: 9781788623797
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime