Test de l’API de l’application Reddit

Test de l'API de l'application Reddit

1. Vue d'ensemble

Nous développons l'API REST deour simple Reddit App depuis un certain temps maintenant - il est temps de devenir sérieux et destart testing it.

Et maintenant que nous avonsfinally switchedvers un mécanisme d'authentification plus simple, il est également plus facile de le faire. Nous allons utiliserthe powerful rest-assured library pour tous ces tests en direct.

2. La configuration initiale

Les tests d'API nécessitent un utilisateur pour s'exécuter; afin de simplifier l'exécution des tests sur l'API, nous allons créer un utilisateur de test au préalable - au démarrage de l'application:

@Component
public class Setup {
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PreferenceRepository preferenceRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @PostConstruct
    private void createTestUser() {
        User userJohn = userRepository.findByUsername("john");
        if (userJohn == null) {
            userJohn = new User();
            userJohn.setUsername("john");
            userJohn.setPassword(passwordEncoder.encode("123"));
            userJohn.setAccessToken("token");
            userRepository.save(userJohn);
            final Preference pref = new Preference();
            pref.setTimezone(TimeZone.getDefault().getID());
            pref.setEmail("[email protected]");
            preferenceRepository.save(pref);
            userJohn.setPreference(pref);
            userRepository.save(userJohn);
        }
    }
}

Remarquez queSetup est un bean simple et que nous utilisons l'annotation@PostConstruct pour accrocher la logique de configuration réelle.

3. Prise en charge des tests en direct

Avant de commencer à écrire nos tests,let’s first set up some basic supporting functionality nous pouvons alors tirer parti.

Nous avons besoin d'éléments tels que l'authentification, les chemins d'URL et peut-être certaines fonctionnalités de traitement et de résolution de problèmes JSON:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  classes = { TestConfig.class },
  loader = AnnotationConfigContextLoader.class)
public class AbstractLiveTest {
    public static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");

    @Autowired
    private CommonPaths commonPaths;

    protected String urlPrefix;

    protected ObjectMapper objectMapper = new ObjectMapper().setDateFormat(dateFormat);

    @Before
    public void setup() {
        urlPrefix = commonPaths.getServerRoot();
    }

    protected RequestSpecification givenAuth() {
        FormAuthConfig formConfig
          = new FormAuthConfig(urlPrefix + "/j_spring_security_check", "username", "password");
        return RestAssured.given().auth().form("john", "123", formConfig);
    }

    protected RequestSpecification withRequestBody(RequestSpecification req, Object obj)
      throws JsonProcessingException {
        return req.contentType(MediaType.APPLICATION_JSON_VALUE)
          .body(objectMapper.writeValueAsString(obj));
    }
}

Nous venons de définir quelques méthodes et champs d'assistance simples pour faciliter les tests:

  • givenAuth(): pour effectuer l'authentification

  • withRequestBody(): pour envoyer la représentation JSON d'unObject comme corps de la requête HTTP

Et voici notre simple bean -CommonPaths - fournissant une abstraction propre aux URL du système:

@Component
@PropertySource({ "classpath:web-${envTarget:local}.properties" })
public class CommonPaths {

    @Value("${http.protocol}")
    private String protocol;

    @Value("${http.port}")
    private String port;

    @Value("${http.host}")
    private String host;

    @Value("${http.address}")
    private String address;

    public String getServerRoot() {
        if (port.equals("80")) {
            return protocol + "://" + host + "/" + address;
        }
        return protocol + "://" + host + ":" + port + "/" + address;
    }
}

Et la version locale du fichier de propriétés:web-local.properties:

http.protocol=http
http.port=8080
http.host=localhost
http.address=reddit-scheduler

Enfin, le test très simple de configuration de Spring:

@Configuration
@ComponentScan({ "org.example.web.live" })
public class TestConfig {
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

4. Tester l'API / scheduledPosts

La première API que nous allons tester est l'API/scheduledPosts:

public class ScheduledPostLiveTest extends AbstractLiveTest {
    private static final String date = "2016-01-01 00:00";

    private Post createPost() throws ParseException, IOException {
        Post post = new Post();
        post.setTitle("test");
        post.setUrl("test.com");
        post.setSubreddit("test");
        post.setSubmissionDate(dateFormat.parse(date));

        Response response = withRequestBody(givenAuth(), post)
          .post(urlPrefix + "/api/scheduledPosts?date=" + date);

        return objectMapper.reader().forType(Post.class).readValue(response.asString());
    }
}

Commençons par testerscheduling a new post:

@Test
public void whenScheduleANewPost_thenCreated()
  throws ParseException, IOException {
    Post post = new Post();
    post.setTitle("test");
    post.setUrl("test.com");
    post.setSubreddit("test");
    post.setSubmissionDate(dateFormat.parse(date));

    Response response = withRequestBody(givenAuth(), post)
      .post(urlPrefix + "/api/scheduledPosts?date=" + date);

    assertEquals(201, response.statusCode());
    Post result = objectMapper.reader().forType(Post.class).readValue(response.asString());
    assertEquals(result.getUrl(), post.getUrl());
}

Ensuite, testonsretrieving all scheduled posts d'un utilisateur:

@Test
public void whenGettingUserScheduledPosts_thenCorrect()
  throws ParseException, IOException {
    createPost();

    Response response = givenAuth().get(urlPrefix + "/api/scheduledPosts?page=0");

    assertEquals(201, response.statusCode());
    assertTrue(response.as(List.class).size() > 0);
}

Ensuite, testonsediting a scheduled post:

@Test
public void whenUpdatingScheduledPost_thenUpdated()
  throws ParseException, IOException {
    Post post = createPost();

    post.setTitle("new title");
    Response response = withRequestBody(givenAuth(), post).
      put(urlPrefix + "/api/scheduledPosts/" + post.getId() + "?date=" + date);

    assertEquals(200, response.statusCode());
    response = givenAuth().get(urlPrefix + "/api/scheduledPosts/" + post.getId());
    assertTrue(response.asString().contains(post.getTitle()));
}

Enfin, testonsthe delete operation dans l'API:

@Test
public void whenDeletingScheduledPost_thenDeleted()
  throws ParseException, IOException {
    Post post = createPost();
    Response response = givenAuth().delete(urlPrefix + "/api/scheduledPosts/" + post.getId());

    assertEquals(204, response.statusCode());
}

5. Tester l'API /sites

Ensuite, testons l'API publiant les ressources Sites - les sites définis par un utilisateur:

public class MySitesLiveTest extends AbstractLiveTest {

    private Site createSite() throws ParseException, IOException {
        Site site = new Site("/feed/");
        site.setName("example");

        Response response = withRequestBody(givenAuth(), site)
          .post(urlPrefix + "/sites");

        return objectMapper.reader().forType(Site.class).readValue(response.asString());
    }
}

Testonsretrieving all the sites de l'utilisateur:

@Test
public void whenGettingUserSites_thenCorrect()
  throws ParseException, IOException {
    createSite();
    Response response = givenAuth().get(urlPrefix + "/sites");

    assertEquals(200, response.statusCode());
    assertTrue(response.as(List.class).size() > 0);
}

Et aussi récupérer les articles d'un site:

@Test
public void whenGettingSiteArticles_thenCorrect()
  throws ParseException, IOException {
    Site site = createSite();
    Response response = givenAuth().get(urlPrefix + "/sites/articles?id=" + site.getId());

    assertEquals(200, response.statusCode());
    assertTrue(response.as(List.class).size() > 0);
}

Ensuite, testonsadding a new Site:

@Test
public void whenAddingNewSite_thenCorrect()
  throws ParseException, IOException {
    Site site = createSite();

    Response response = givenAuth().get(urlPrefix + "/sites");
    assertTrue(response.asString().contains(site.getUrl()));
}

Etdeleting c'est:

@Test
public void whenDeletingSite_thenDeleted() throws ParseException, IOException {
    Site site = createSite();
    Response response = givenAuth().delete(urlPrefix + "/sites/" + site.getId());

    assertEquals(204, response.statusCode());
}

6. Tester l'API/user/preferences

Enfin, concentrons-nous sur l'API exposant les préférences de l'utilisateur.

Commençons par testergetting user’s preferences:

@Test
public void whenGettingPrefernce_thenCorrect() {
    Response response = givenAuth().get(urlPrefix + "/user/preference");

    assertEquals(200, response.statusCode());
    assertTrue(response.as(Preference.class).getEmail().contains("john"));
}

Etediting eux:

@Test
public void whenUpdattingPrefernce_thenCorrect()
  throws JsonProcessingException {
    Preference pref = givenAuth().get(urlPrefix + "/user/preference").as(Preference.class);
    pref.setEmail("[email protected]");
    Response response = withRequestBody(givenAuth(), pref).
      put(urlPrefix + "/user/preference/" + pref.getId());

    assertEquals(200, response.statusCode());
    response = givenAuth().get(urlPrefix + "/user/preference");
    assertEquals(response.as(Preference.class).getEmail(), pref.getEmail());
}

7. Conclusion

Dans cet article rapide, nous avons rassemblé des tests de base pour notre API REST.

Rien d'extraordinaire cependant - des scénarios plus avancés sont nécessaires - maisthis isn’t about perfection, it’s about progress and iterating in public.