DEV Community

Cover image for Alexa Skills Testing with Java
Tomer Figenblat
Tomer Figenblat

Posted on • Edited on

Alexa Skills Testing with Java

Test your Java Alexa skills

This tutorial will help you write automated test cases for Alexa skills written in Java. Using real-life JSON requests and verifying multi-turn interactions without deploying or hosting the skill.

To use the alexa-skills-tester, just add the following to your project:
(replace VERSION with the current release).

<dependency>
  <groupId>info.tomfi.alexa</groupId>
  <artifactId>alexa-skills-tester</artifactId>
  <version>VERSION</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Example usage

Using a basic Nice to meet you custom skill:
When launching the skill, it asks for the user name and then replies with a Nice to meet you #name prompt, ending the session.

Let's take a look at the code for the skill.

A launch request handler

public final class LaunchRequestHandlerImpl implements LaunchRequestHandler {
  @Override
  public boolean canHandle(final HandlerInput input, final LaunchRequest request) {
    return true;
  }

  @Override
  public Optional<Response> handle(final HandlerInput input, final LaunchRequest request) {
    return input.getResponseBuilder()
        .withSpeech("What is your name?")
        .withReprompt("Please tell me your name.")
        .withShouldEndSession(false)
        .build();
  }
}
Enter fullscreen mode Exit fullscreen mode

An intent request handler handling MyNameIntent with a nameSlot

public final class MyNameIntentRequestHandler implements IntentRequestHandler {
  @Override
  public boolean canHandle(final HandlerInput input, final IntentRequest request) {
    return request.getIntent().getName().equals("MyNameIntent");
  }

  @Override
  public Optional<Response> handle(final HandlerInput input, final IntentRequest request) {
    var name = request.getIntent().getSlots().get("nameSlot").getValue();
    return input.getResponseBuilder()
        .withSpeech(String.format("Nice to meet you %s!", name))
        .withShouldEndSession(true)
        .build();
  }
}
Enter fullscreen mode Exit fullscreen mode

A session ended request handler

public final class SessionEndedRequestHandlerImpl implements SessionEndedRequestHandler {
  @Override
  public boolean canHandle(final HandlerInput input, final SessionEndedRequest request) {
    return true;
  }

  @Override
  public Optional<Response> handle(final HandlerInput input, final SessionEndedRequest request) {
    return Optional.empty();
  }
}
Enter fullscreen mode Exit fullscreen mode

A utility class for constructing the skill

class NiceToMeetYouSkill {
  public static Skill getSkill() {
    return Skills.standard()
        .addRequestHandler(new LaunchRequestHandlerImpl())
        .addRequestHandler(new MyNameIntentRequestHandler())
        .addRequestHandler(new SessionEndedRequestHandlerImpl())
        .build();
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, let's test the skill

You should get familiarized with Alexa's Request and Response JSON Reference. This is, of course, should be considered common knowledge for skill developers.

Let's create a couple of fake request JSON files to use in our tests.

launch_request.json

{
  "version": "1.0",
  "session": {
    "new": true
  },
  "context": {
    "System": {}
  },
  "request": {
    "type": "LaunchRequest",
    "requestId": "amzn1.echo-api.request.fake-request-id",
    "timestamp": "2021-02-11T15:30:00Z",
    "locale": "en-US"
  }
}

Enter fullscreen mode Exit fullscreen mode

my_name_intent.json

{
  "version": "1.0",
  "session": {
    "new": false
  },
  "context": {
    "System": {}
  },
  "request": {
    "type": "IntentRequest",
    "requestId": "amzn1.echo-api.request.fake-request-id",
    "timestamp": "2021-02-11T15:31:00Z",
    "locale": "en-US",
    "intent": {
      "name": "MyNameIntent",
      "slots": {
        "nameSlot": {
          "name": "nameSlot",
          "value": "master"
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

session_ended.json

{
    "version": "1.0",
    "session": {
        "new": false
    },
    "context": {
        "System": {}
    },
    "request": {
        "type": "SessionEndedRequest",
        "requestId": "amzn1.echo-api.request.fake-request-id",
        "timestamp": "2021-02-11T15:32:00Z",
        "locale": "en-US"
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, let's create some test cases leveraging the above JSON files to verify the interaction with the skill.

Test cases testing the Nice to meet you skill

final class SkillInteractionTest {
  @Test
  void verify_greeting_for_my_name_intent_as_a_followup_to_a_launch_request() throws IOException {
    givenSkill(NiceToMeetYouSkill.getSkill())
        .whenRequestIs(getClass().getClassLoader().getResourceAsStream("launch_request.json").readAllBytes())
        .thenResponseShould()
            .waitForFollowup()
            .haveOutputSpeechOf("What is your name?")
            .haveRepromptSpeechOf("Please tell me your name.")
        .followingUpWith(getClass().getClassLoader().getResourceAsStream("my_name_intent.json").readAllBytes())
        .thenResponseShould()
            .haveOutputSpeechOf("Nice to meet you master!")
            .and()
            .notWaitForFollowup();
  }

  @Test
  void verify_empty_response_for_session_ended_requests() throws IOException {
    givenSkill(NiceToMeetYouSkill.getSkill())
      .whenRequestIs(getClass().getClassLoader().getResourceAsStream("launch_request.json").readAllBytes())
      .thenResponseShould()
          .waitForFollowup()
          .haveOutputSpeechOf("What is your name?")
          .haveRepromptSpeechOf("Please tell me your name.")
      .followingUpWith(getClass().getClassLoader().getResourceAsStream("session_ended.json").readAllBytes())
      .thenResponseShould().beEmpty();
  }
}
Enter fullscreen mode Exit fullscreen mode

That's it!
I hope you'll find this tool useful.

Top comments (1)

Collapse
 
kini profile image
Joaquín Engelmo (a.k.a. Kini)

Great post @tomerfi :)

I will give a try in my skills!

Thanks