JHipster code review for generated Prelegent entity


Hi All,
today we will take a quick look at code generated with JHipster. Is console generator our friend? What JHipster can generate for us?

Last week we had generated basic entity Prelegent using command line generator. Now I’d like to show you how look like the code responsible for those functionalities. When I was testing some other generators like Spring Roo, it drew my attention to the fact that generated code was simply unclear, with lots of Aspects and hard to be modified.

In JHipster I think code is much better and more clear than in other solutions. You can choose most of technologies, starting from backend (REST, dao/dto), to frontend (Angular 1, Angular 2, React, Vue), searching (standard with DB, elasticsearch). On the end of this project we will see is it worth to be mentioned.

Spis treści

Main panel

Getting back to our application. Starting from main page, generator has added our entity to the list of entities.

<ul class="dropdown-menu" uib-dropdown-menu>
	<li ui-sref-active="active">
		<a ui-sref="prelegent" ng-click="vm.collapseNavbar()">
			<span class="glyphicon glyphicon-asterisk"></span>&nbsp;
			<span data-translate="global.menu.entities.prelegent">Prelegent</span>
		</a>
	</li>
	<!-- jhipster-needle-add-entity-to-menu - JHipster will add entities to the menu here -->
</ul>

How it looks in java

What we got is base domain entity Prelegent with implemented communication logic via REST for Angular. JHipster generated for us basic repository class which extends generic basic Spring Data JPA CRUD operations. Please take a look below, code is quite clean and gets positive impression for developer, who will need to use and extend it 😉

package com.pdb.eventsearch.domain;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.springframework.data.elasticsearch.annotations.Document;

import javax.persistence.*;
import javax.validation.constraints.*;
import java.io.Serializable;
import java.util.Objects;

/**
 * A Prelegent.
 */
@Entity
@Table(name = "prelegent")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Document(indexName = "prelegent")
public class Prelegent implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
    @SequenceGenerator(name = "sequenceGenerator")
    private Long id;

    @NotNull
    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "bio")
    private String bio;

    @Column(name = "website_url")
    private String websiteUrl;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public Prelegent name(String name) {
        this.name = name;
        return this;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBio() {
        return bio;
    }

    public Prelegent bio(String bio) {
        this.bio = bio;
        return this;
    }

    public void setBio(String bio) {
        this.bio = bio;
    }

    public String getWebsiteUrl() {
        return websiteUrl;
    }

    public Prelegent websiteUrl(String websiteUrl) {
        this.websiteUrl = websiteUrl;
        return this;
    }

    public void setWebsiteUrl(String websiteUrl) {
        this.websiteUrl = websiteUrl;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Prelegent prelegent = (Prelegent) o;
        if (prelegent.id == null || id == null) {
            return false;
        }
        return Objects.equals(id, prelegent.id);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(id);
    }

    @Override
    public String toString() {
        return "Prelegent{" +
            "id=" + id +
            ", name='" + name + "'" +
            ", bio='" + bio + "'" +
            ", websiteUrl='" + websiteUrl + "'" +
            '}';
    }
}

REST resource file:

package com.pdb.eventsearch.web.rest;

import com.codahale.metrics.annotation.Timed;
import com.pdb.eventsearch.domain.Prelegent;

import com.pdb.eventsearch.repository.PrelegentRepository;
import com.pdb.eventsearch.repository.search.PrelegentSearchRepository;
import com.pdb.eventsearch.web.rest.util.HeaderUtil;
import com.pdb.eventsearch.web.rest.util.PaginationUtil;
import io.swagger.annotations.ApiParam;
import io.github.jhipster.web.util.ResponseUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static org.elasticsearch.index.query.QueryBuilders.*;

/**
 * REST controller for managing Prelegent.
 */
@RestController
@RequestMapping("/api")
public class PrelegentResource {

    private final Logger log = LoggerFactory.getLogger(PrelegentResource.class);

    private static final String ENTITY_NAME = "prelegent";
        
    private final PrelegentRepository prelegentRepository;

    private final PrelegentSearchRepository prelegentSearchRepository;

    public PrelegentResource(PrelegentRepository prelegentRepository, PrelegentSearchRepository prelegentSearchRepository) {
        this.prelegentRepository = prelegentRepository;
        this.prelegentSearchRepository = prelegentSearchRepository;
    }

    /**
     * POST  /prelegents : Create a new prelegent.
     *
     * @param prelegent the prelegent to create
     * @return the ResponseEntity with status 201 (Created) and with body the new prelegent, or with status 400 (Bad Request) if the prelegent has already an ID
     * @throws URISyntaxException if the Location URI syntax is incorrect
     */
    @PostMapping("/prelegents")
    @Timed
    public ResponseEntity<Prelegent> createPrelegent(@Valid @RequestBody Prelegent prelegent) throws URISyntaxException {
        log.debug("REST request to save Prelegent : {}", prelegent);
        if (prelegent.getId() != null) {
            return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "idexists", "A new prelegent cannot already have an ID")).body(null);
        }
        Prelegent result = prelegentRepository.save(prelegent);
        prelegentSearchRepository.save(result);
        return ResponseEntity.created(new URI("/api/prelegents/" + result.getId()))
            .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
            .body(result);
    }

    /**
     * PUT  /prelegents : Updates an existing prelegent.
     *
     * @param prelegent the prelegent to update
     * @return the ResponseEntity with status 200 (OK) and with body the updated prelegent,
     * or with status 400 (Bad Request) if the prelegent is not valid,
     * or with status 500 (Internal Server Error) if the prelegent couldnt be updated
     * @throws URISyntaxException if the Location URI syntax is incorrect
     */
    @PutMapping("/prelegents")
    @Timed
    public ResponseEntity<Prelegent> updatePrelegent(@Valid @RequestBody Prelegent prelegent) throws URISyntaxException {
        log.debug("REST request to update Prelegent : {}", prelegent);
        if (prelegent.getId() == null) {
            return createPrelegent(prelegent);
        }
        Prelegent result = prelegentRepository.save(prelegent);
        prelegentSearchRepository.save(result);
        return ResponseEntity.ok()
            .headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, prelegent.getId().toString()))
            .body(result);
    }

    /**
     * GET  /prelegents : get all the prelegents.
     *
     * @param pageable the pagination information
     * @return the ResponseEntity with status 200 (OK) and the list of prelegents in body
     * @throws URISyntaxException if there is an error to generate the pagination HTTP headers
     */
    @GetMapping("/prelegents")
    @Timed
    public ResponseEntity<List<Prelegent>> getAllPrelegents(@ApiParam Pageable pageable) {
        log.debug("REST request to get a page of Prelegents");
        Page<Prelegent> page = prelegentRepository.findAll(pageable);
        HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/prelegents");
        return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
    }

    /**
     * GET  /prelegents/:id : get the "id" prelegent.
     *
     * @param id the id of the prelegent to retrieve
     * @return the ResponseEntity with status 200 (OK) and with body the prelegent, or with status 404 (Not Found)
     */
    @GetMapping("/prelegents/{id}")
    @Timed
    public ResponseEntity<Prelegent> getPrelegent(@PathVariable Long id) {
        log.debug("REST request to get Prelegent : {}", id);
        Prelegent prelegent = prelegentRepository.findOne(id);
        return ResponseUtil.wrapOrNotFound(Optional.ofNullable(prelegent));
    }

    /**
     * DELETE  /prelegents/:id : delete the "id" prelegent.
     *
     * @param id the id of the prelegent to delete
     * @return the ResponseEntity with status 200 (OK)
     */
    @DeleteMapping("/prelegents/{id}")
    @Timed
    public ResponseEntity<Void> deletePrelegent(@PathVariable Long id) {
        log.debug("REST request to delete Prelegent : {}", id);
        prelegentRepository.delete(id);
        prelegentSearchRepository.delete(id);
        return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(ENTITY_NAME, id.toString())).build();
    }

    /**
     * SEARCH  /_search/prelegents?query=:query : search for the prelegent corresponding
     * to the query.
     *
     * @param query the query of the prelegent search 
     * @param pageable the pagination information
     * @return the result of the search
     * @throws URISyntaxException if there is an error to generate the pagination HTTP headers
     */
    @GetMapping("/_search/prelegents")
    @Timed
    public ResponseEntity<List<Prelegent>> searchPrelegents(@RequestParam String query, @ApiParam Pageable pageable) {
        log.debug("REST request to search for a page of Prelegents for query {}", query);
        Page<Prelegent> page = prelegentSearchRepository.search(queryStringQuery(query), pageable);
        HttpHeaders headers = PaginationUtil.generateSearchPaginationHttpHeaders(query, page, "/api/_search/prelegents");
        return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
    }


}

List of Prelegents

Generated controller for Prelegent entity:

(function() {
    'use strict';

    angular
        .module('eventsearchApp')
        .controller('PrelegentController', PrelegentController);

    PrelegentController.$inject = ['$state', 'Prelegent', 'PrelegentSearch', 'ParseLinks', 'AlertService', 'paginationConstants', 'pagingParams'];

    function PrelegentController($state, Prelegent, PrelegentSearch, ParseLinks, AlertService, paginationConstants, pagingParams) {

        var vm = this;

        vm.loadPage = loadPage;
        vm.predicate = pagingParams.predicate;
        vm.reverse = pagingParams.ascending;
        vm.transition = transition;
        vm.itemsPerPage = paginationConstants.itemsPerPage;
        vm.clear = clear;
        vm.search = search;
        vm.loadAll = loadAll;
        vm.searchQuery = pagingParams.search;
        vm.currentSearch = pagingParams.search;

        loadAll();

        function loadAll () {
            if (pagingParams.search) {
                PrelegentSearch.query({
                    query: pagingParams.search,
                    page: pagingParams.page - 1,
                    size: vm.itemsPerPage,
                    sort: sort()
                }, onSuccess, onError);
            } else {
                Prelegent.query({
                    page: pagingParams.page - 1,
                    size: vm.itemsPerPage,
                    sort: sort()
                }, onSuccess, onError);
            }
            function sort() {
                var result = [vm.predicate + ',' + (vm.reverse ? 'asc' : 'desc')];
                if (vm.predicate !== 'id') {
                    result.push('id');
                }
                return result;
            }
            function onSuccess(data, headers) {
                vm.links = ParseLinks.parse(headers('link'));
                vm.totalItems = headers('X-Total-Count');
                vm.queryCount = vm.totalItems;
                vm.prelegents = data;
                vm.page = pagingParams.page;
            }
            function onError(error) {
                AlertService.error(error.data.message);
            }
        }

        function loadPage(page) {
            vm.page = page;
            vm.transition();
        }

        function transition() {
            $state.transitionTo($state.$current, {
                page: vm.page,
                sort: vm.predicate + ',' + (vm.reverse ? 'asc' : 'desc'),
                search: vm.currentSearch
            });
        }

        function search(searchQuery) {
            if (!searchQuery){
                return vm.clear();
            }
            vm.links = null;
            vm.page = 1;
            vm.predicate = '_score';
            vm.reverse = false;
            vm.currentSearch = searchQuery;
            vm.transition();
        }

        function clear() {
            vm.links = null;
            vm.page = 1;
            vm.predicate = 'id';
            vm.reverse = true;
            vm.currentSearch = null;
            vm.transition();
        }
    }
})();

You can display data with HTML template:

<div>
    <h2 data-translate="eventsearchApp.prelegent.home.title">Prelegents</h2>
    <jhi-alert></jhi-alert>
    <div class="container-fluid">
        <div class="row">
            <div class="col-xs-4 no-padding-left">
                <button class="btn btn-primary" ui-sref="prelegent.new" >
                    <span class="glyphicon glyphicon-plus"></span>
                    <span class="hidden-xs-down"  data-translate="eventsearchApp.prelegent.home.createLabel">
                        Create new Prelegent
                    </span>
                </button>
            </div>
            <div class="col-xs-8 no-padding-right">
                <form name="searchForm" class="form-inline">
                    <div class="input-group pull-right" >
                        <input type="text" class="form-control" ng-model="vm.searchQuery" id="searchQuery" placeholder="{{ 'eventsearchApp.prelegent.home.search' | translate }}">
                        <span  class="input-group-btn width-min" >
                            <button class="btn btn-info" ng-click="vm.search(vm.searchQuery)">
                                <span class="glyphicon glyphicon-search"></span>
                            </button>
                        </span>
                        <span class="input-group-btn width-min" ng-if="vm.currentSearch">
                            <button class="btn btn-info" ng-click="vm.clear()">
                                <span class="glyphicon glyphicon-trash"></span>
                            </button>
                        </span>
                    </div>
                </form>
            </div>
        </div>
    </div>
    <br/>
    <div class="table-responsive">
        <table class="jh-table table table-striped">
            <thead>
                <tr jh-sort="vm.predicate" ascending="vm.reverse" callback="vm.transition()">
                    <th jh-sort-by="id"><span data-translate="global.field.id">ID</span> <span class="glyphicon glyphicon-sort"></span></th>
                    <th jh-sort-by="name"><span data-translate="eventsearchApp.prelegent.name">Name</span> <span class="glyphicon glyphicon-sort"></span></th>
                    <th jh-sort-by="bio"><span data-translate="eventsearchApp.prelegent.bio">Bio</span> <span class="glyphicon glyphicon-sort"></span></th>
                    <th jh-sort-by="websiteUrl"><span data-translate="eventsearchApp.prelegent.websiteUrl">Website Url</span> <span class="glyphicon glyphicon-sort"></span></th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="prelegent in vm.prelegents track by prelegent.id">
                    <td><a ui-sref="prelegent-detail({id:prelegent.id})">{{prelegent.id}}</a></td>
                    <td>{{prelegent.name}}</td>
                    <td>{{prelegent.bio}}</td>
                    <td>{{prelegent.websiteUrl}}</td>
                    <td class="text-right">
                        <div class="btn-group flex-btn-group-container">
                            <button type="submit"
                                    ui-sref="prelegent-detail({id:prelegent.id})"
                                    class="btn btn-info btn-sm">
                                <span class="glyphicon glyphicon-eye-open"></span>
                                <span class="hidden-sm-down" data-translate="entity.action.view"></span>
                            </button>
                            <button type="submit"
                                    ui-sref="prelegent.edit({id:prelegent.id})"
                                    class="btn btn-primary btn-sm">
                                <span class="glyphicon glyphicon-pencil"></span>
                                <span class="hidden-sm-down" data-translate="entity.action.edit"></span>
                            </button>
                            <button type="submit"
                                    ui-sref="prelegent.delete({id:prelegent.id})"
                                    class="btn btn-danger btn-sm">
                                <span class="glyphicon glyphicon-remove-circle"></span>
                                <span class="hidden-sm-down" data-translate="entity.action.delete"></span>
                            </button>
                        </div>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
    <div class="text-center">
        <jhi-item-count page="vm.page" total="vm.queryCount" items-per-page="vm.itemsPerPage"></jhi-item-count>
        <uib-pagination class="pagination-sm" total-items="vm.totalItems" items-per-page="vm.itemsPerPage" ng-model="vm.page" ng-change="vm.transition()"></uib-pagination>
    </div>
</div>

Routing file for Angular 1 with handling state changes:

(function() {
    'use strict';

    angular
        .module('eventsearchApp')
        .config(stateConfig);

    stateConfig.$inject = ['$stateProvider'];

    function stateConfig($stateProvider) {
        $stateProvider
        .state('prelegent', {
            parent: 'entity',
            url: '/prelegent?page&sort&search',
            data: {
                authorities: ['ROLE_USER'],
                pageTitle: 'eventsearchApp.prelegent.home.title'
            },
            views: {
                'content@': {
                    templateUrl: 'app/entities/prelegent/prelegents.html',
                    controller: 'PrelegentController',
                    controllerAs: 'vm'
                }
            },
            params: {
                page: {
                    value: '1',
                    squash: true
                },
                sort: {
                    value: 'id,asc',
                    squash: true
                },
                search: null
            },
            resolve: {
                pagingParams: ['$stateParams', 'PaginationUtil', function ($stateParams, PaginationUtil) {
                    return {
                        page: PaginationUtil.parsePage($stateParams.page),
                        sort: $stateParams.sort,
                        predicate: PaginationUtil.parsePredicate($stateParams.sort),
                        ascending: PaginationUtil.parseAscending($stateParams.sort),
                        search: $stateParams.search
                    };
                }],
                translatePartialLoader: ['$translate', '$translatePartialLoader', function ($translate, $translatePartialLoader) {
                    $translatePartialLoader.addPart('prelegent');
                    $translatePartialLoader.addPart('global');
                    return $translate.refresh();
                }]
            }
        })
        .state('prelegent-detail', {
            parent: 'prelegent',
            url: '/prelegent/{id}',
            data: {
                authorities: ['ROLE_USER'],
                pageTitle: 'eventsearchApp.prelegent.detail.title'
            },
            views: {
                'content@': {
                    templateUrl: 'app/entities/prelegent/prelegent-detail.html',
                    controller: 'PrelegentDetailController',
                    controllerAs: 'vm'
                }
            },
            resolve: {
                translatePartialLoader: ['$translate', '$translatePartialLoader', function ($translate, $translatePartialLoader) {
                    $translatePartialLoader.addPart('prelegent');
                    return $translate.refresh();
                }],
                entity: ['$stateParams', 'Prelegent', function($stateParams, Prelegent) {
                    return Prelegent.get({id : $stateParams.id}).$promise;
                }],
                previousState: ["$state", function ($state) {
                    var currentStateData = {
                        name: $state.current.name || 'prelegent',
                        params: $state.params,
                        url: $state.href($state.current.name, $state.params)
                    };
                    return currentStateData;
                }]
            }
        })
        .state('prelegent-detail.edit', {
            parent: 'prelegent-detail',
            url: '/detail/edit',
            data: {
                authorities: ['ROLE_USER']
            },
            onEnter: ['$stateParams', '$state', '$uibModal', function($stateParams, $state, $uibModal) {
                $uibModal.open({
                    templateUrl: 'app/entities/prelegent/prelegent-dialog.html',
                    controller: 'PrelegentDialogController',
                    controllerAs: 'vm',
                    backdrop: 'static',
                    size: 'lg',
                    resolve: {
                        entity: ['Prelegent', function(Prelegent) {
                            return Prelegent.get({id : $stateParams.id}).$promise;
                        }]
                    }
                }).result.then(function() {
                    $state.go('^', {}, { reload: false });
                }, function() {
                    $state.go('^');
                });
            }]
        })
        .state('prelegent.new', {
            parent: 'prelegent',
            url: '/new',
            data: {
                authorities: ['ROLE_USER']
            },
            onEnter: ['$stateParams', '$state', '$uibModal', function($stateParams, $state, $uibModal) {
                $uibModal.open({
                    templateUrl: 'app/entities/prelegent/prelegent-dialog.html',
                    controller: 'PrelegentDialogController',
                    controllerAs: 'vm',
                    backdrop: 'static',
                    size: 'lg',
                    resolve: {
                        entity: function () {
                            return {
                                name: null,
                                bio: null,
                                websiteUrl: null,
                                id: null
                            };
                        }
                    }
                }).result.then(function() {
                    $state.go('prelegent', null, { reload: 'prelegent' });
                }, function() {
                    $state.go('prelegent');
                });
            }]
        })
        .state('prelegent.edit', {
            parent: 'prelegent',
            url: '/{id}/edit',
            data: {
                authorities: ['ROLE_USER']
            },
            onEnter: ['$stateParams', '$state', '$uibModal', function($stateParams, $state, $uibModal) {
                $uibModal.open({
                    templateUrl: 'app/entities/prelegent/prelegent-dialog.html',
                    controller: 'PrelegentDialogController',
                    controllerAs: 'vm',
                    backdrop: 'static',
                    size: 'lg',
                    resolve: {
                        entity: ['Prelegent', function(Prelegent) {
                            return Prelegent.get({id : $stateParams.id}).$promise;
                        }]
                    }
                }).result.then(function() {
                    $state.go('prelegent', null, { reload: 'prelegent' });
                }, function() {
                    $state.go('^');
                });
            }]
        })
        .state('prelegent.delete', {
            parent: 'prelegent',
            url: '/{id}/delete',
            data: {
                authorities: ['ROLE_USER']
            },
            onEnter: ['$stateParams', '$state', '$uibModal', function($stateParams, $state, $uibModal) {
                $uibModal.open({
                    templateUrl: 'app/entities/prelegent/prelegent-delete-dialog.html',
                    controller: 'PrelegentDeleteController',
                    controllerAs: 'vm',
                    size: 'md',
                    resolve: {
                        entity: ['Prelegent', function(Prelegent) {
                            return Prelegent.get({id : $stateParams.id}).$promise;
                        }]
                    }
                }).result.then(function() {
                    $state.go('prelegent', null, { reload: 'prelegent' });
                }, function() {
                    $state.go('^');
                });
            }]
        });
    }

})();

Searching mechanism

For the beginning we have our basic Repository and SearchReposotiry classes. First one is is based on Spring Data JPA with it’s pros as JpaRepository parent class:

package com.pdb.eventsearch.repository;

import com.pdb.eventsearch.domain.Prelegent;

import org.springframework.data.jpa.repository.*;

import java.util.List;

/**
 * Spring Data JPA repository for the Prelegent entity.
 */
@SuppressWarnings("unused")
public interface PrelegentRepository extends JpaRepository<Prelegent,Long> {

}

And ElasticSearch implementation of searching mechanism:

package com.pdb.eventsearch.repository.search;

import com.pdb.eventsearch.domain.Prelegent;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

/**
 * Spring Data Elasticsearch repository for the Prelegent entity.
 */
public interface PrelegentSearchRepository extends ElasticsearchRepository<Prelegent, Long> {
}

 

Tests

For generated entity we also was given by Authors with lots of tests. From java perspective we have test if entity resource works:

package com.pdb.eventsearch.web.rest;

import com.pdb.eventsearch.EventsearchApp;

import com.pdb.eventsearch.domain.Prelegent;
import com.pdb.eventsearch.repository.PrelegentRepository;
import com.pdb.eventsearch.repository.search.PrelegentSearchRepository;
import com.pdb.eventsearch.web.rest.errors.ExceptionTranslator;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.hasItem;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * Test class for the PrelegentResource REST controller.
 *
 * @see PrelegentResource
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = EventsearchApp.class)
public class PrelegentResourceIntTest {

    private static final String DEFAULT_NAME = "AAAAAAAAAA";
    private static final String UPDATED_NAME = "BBBBBBBBBB";

    private static final String DEFAULT_BIO = "AAAAAAAAAA";
    private static final String UPDATED_BIO = "BBBBBBBBBB";

    private static final String DEFAULT_WEBSITE_URL = "AAAAAAAAAA";
    private static final String UPDATED_WEBSITE_URL = "BBBBBBBBBB";

    @Autowired
    private PrelegentRepository prelegentRepository;

    @Autowired
    private PrelegentSearchRepository prelegentSearchRepository;

    @Autowired
    private MappingJackson2HttpMessageConverter jacksonMessageConverter;

    @Autowired
    private PageableHandlerMethodArgumentResolver pageableArgumentResolver;

    @Autowired
    private ExceptionTranslator exceptionTranslator;

    @Autowired
    private EntityManager em;

    private MockMvc restPrelegentMockMvc;

    private Prelegent prelegent;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        PrelegentResource prelegentResource = new PrelegentResource(prelegentRepository, prelegentSearchRepository);
        this.restPrelegentMockMvc = MockMvcBuilders.standaloneSetup(prelegentResource)
            .setCustomArgumentResolvers(pageableArgumentResolver)
            .setControllerAdvice(exceptionTranslator)
            .setMessageConverters(jacksonMessageConverter).build();
    }

    /**
     * Create an entity for this test.
     *
     * This is a static method, as tests for other entities might also need it,
     * if they test an entity which requires the current entity.
     */
    public static Prelegent createEntity(EntityManager em) {
        Prelegent prelegent = new Prelegent()
            .name(DEFAULT_NAME)
            .bio(DEFAULT_BIO)
            .websiteUrl(DEFAULT_WEBSITE_URL);
        return prelegent;
    }

    @Before
    public void initTest() {
        prelegentSearchRepository.deleteAll();
        prelegent = createEntity(em);
    }

    @Test
    @Transactional
    public void createPrelegent() throws Exception {
        int databaseSizeBeforeCreate = prelegentRepository.findAll().size();

        // Create the Prelegent
        restPrelegentMockMvc.perform(post("/api/prelegents")
            .contentType(TestUtil.APPLICATION_JSON_UTF8)
            .content(TestUtil.convertObjectToJsonBytes(prelegent)))
            .andExpect(status().isCreated());

        // Validate the Prelegent in the database
        List<Prelegent> prelegentList = prelegentRepository.findAll();
        assertThat(prelegentList).hasSize(databaseSizeBeforeCreate + 1);
        Prelegent testPrelegent = prelegentList.get(prelegentList.size() - 1);
        assertThat(testPrelegent.getName()).isEqualTo(DEFAULT_NAME);
        assertThat(testPrelegent.getBio()).isEqualTo(DEFAULT_BIO);
        assertThat(testPrelegent.getWebsiteUrl()).isEqualTo(DEFAULT_WEBSITE_URL);

        // Validate the Prelegent in Elasticsearch
        Prelegent prelegentEs = prelegentSearchRepository.findOne(testPrelegent.getId());
        assertThat(prelegentEs).isEqualToComparingFieldByField(testPrelegent);
    }

    @Test
    @Transactional
    public void createPrelegentWithExistingId() throws Exception {
        int databaseSizeBeforeCreate = prelegentRepository.findAll().size();

        // Create the Prelegent with an existing ID
        prelegent.setId(1L);

        // An entity with an existing ID cannot be created, so this API call must fail
        restPrelegentMockMvc.perform(post("/api/prelegents")
            .contentType(TestUtil.APPLICATION_JSON_UTF8)
            .content(TestUtil.convertObjectToJsonBytes(prelegent)))
            .andExpect(status().isBadRequest());

        // Validate the Alice in the database
        List<Prelegent> prelegentList = prelegentRepository.findAll();
        assertThat(prelegentList).hasSize(databaseSizeBeforeCreate);
    }

    @Test
    @Transactional
    public void checkNameIsRequired() throws Exception {
        int databaseSizeBeforeTest = prelegentRepository.findAll().size();
        // set the field null
        prelegent.setName(null);

        // Create the Prelegent, which fails.

        restPrelegentMockMvc.perform(post("/api/prelegents")
            .contentType(TestUtil.APPLICATION_JSON_UTF8)
            .content(TestUtil.convertObjectToJsonBytes(prelegent)))
            .andExpect(status().isBadRequest());

        List<Prelegent> prelegentList = prelegentRepository.findAll();
        assertThat(prelegentList).hasSize(databaseSizeBeforeTest);
    }

    @Test
    @Transactional
    public void getAllPrelegents() throws Exception {
        // Initialize the database
        prelegentRepository.saveAndFlush(prelegent);

        // Get all the prelegentList
        restPrelegentMockMvc.perform(get("/api/prelegents?sort=id,desc"))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
            .andExpect(jsonPath("$.[*].id").value(hasItem(prelegent.getId().intValue())))
            .andExpect(jsonPath("$.[*].name").value(hasItem(DEFAULT_NAME.toString())))
            .andExpect(jsonPath("$.[*].bio").value(hasItem(DEFAULT_BIO.toString())))
            .andExpect(jsonPath("$.[*].websiteUrl").value(hasItem(DEFAULT_WEBSITE_URL.toString())));
    }

    @Test
    @Transactional
    public void getPrelegent() throws Exception {
        // Initialize the database
        prelegentRepository.saveAndFlush(prelegent);

        // Get the prelegent
        restPrelegentMockMvc.perform(get("/api/prelegents/{id}", prelegent.getId()))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
            .andExpect(jsonPath("$.id").value(prelegent.getId().intValue()))
            .andExpect(jsonPath("$.name").value(DEFAULT_NAME.toString()))
            .andExpect(jsonPath("$.bio").value(DEFAULT_BIO.toString()))
            .andExpect(jsonPath("$.websiteUrl").value(DEFAULT_WEBSITE_URL.toString()));
    }

    @Test
    @Transactional
    public void getNonExistingPrelegent() throws Exception {
        // Get the prelegent
        restPrelegentMockMvc.perform(get("/api/prelegents/{id}", Long.MAX_VALUE))
            .andExpect(status().isNotFound());
    }

    @Test
    @Transactional
    public void updatePrelegent() throws Exception {
        // Initialize the database
        prelegentRepository.saveAndFlush(prelegent);
        prelegentSearchRepository.save(prelegent);
        int databaseSizeBeforeUpdate = prelegentRepository.findAll().size();

        // Update the prelegent
        Prelegent updatedPrelegent = prelegentRepository.findOne(prelegent.getId());
        updatedPrelegent
            .name(UPDATED_NAME)
            .bio(UPDATED_BIO)
            .websiteUrl(UPDATED_WEBSITE_URL);

        restPrelegentMockMvc.perform(put("/api/prelegents")
            .contentType(TestUtil.APPLICATION_JSON_UTF8)
            .content(TestUtil.convertObjectToJsonBytes(updatedPrelegent)))
            .andExpect(status().isOk());

        // Validate the Prelegent in the database
        List<Prelegent> prelegentList = prelegentRepository.findAll();
        assertThat(prelegentList).hasSize(databaseSizeBeforeUpdate);
        Prelegent testPrelegent = prelegentList.get(prelegentList.size() - 1);
        assertThat(testPrelegent.getName()).isEqualTo(UPDATED_NAME);
        assertThat(testPrelegent.getBio()).isEqualTo(UPDATED_BIO);
        assertThat(testPrelegent.getWebsiteUrl()).isEqualTo(UPDATED_WEBSITE_URL);

        // Validate the Prelegent in Elasticsearch
        Prelegent prelegentEs = prelegentSearchRepository.findOne(testPrelegent.getId());
        assertThat(prelegentEs).isEqualToComparingFieldByField(testPrelegent);
    }

    @Test
    @Transactional
    public void updateNonExistingPrelegent() throws Exception {
        int databaseSizeBeforeUpdate = prelegentRepository.findAll().size();

        // Create the Prelegent

        // If the entity doesn't have an ID, it will be created instead of just being updated
        restPrelegentMockMvc.perform(put("/api/prelegents")
            .contentType(TestUtil.APPLICATION_JSON_UTF8)
            .content(TestUtil.convertObjectToJsonBytes(prelegent)))
            .andExpect(status().isCreated());

        // Validate the Prelegent in the database
        List<Prelegent> prelegentList = prelegentRepository.findAll();
        assertThat(prelegentList).hasSize(databaseSizeBeforeUpdate + 1);
    }

    @Test
    @Transactional
    public void deletePrelegent() throws Exception {
        // Initialize the database
        prelegentRepository.saveAndFlush(prelegent);
        prelegentSearchRepository.save(prelegent);
        int databaseSizeBeforeDelete = prelegentRepository.findAll().size();

        // Get the prelegent
        restPrelegentMockMvc.perform(delete("/api/prelegents/{id}", prelegent.getId())
            .accept(TestUtil.APPLICATION_JSON_UTF8))
            .andExpect(status().isOk());

        // Validate Elasticsearch is empty
        boolean prelegentExistsInEs = prelegentSearchRepository.exists(prelegent.getId());
        assertThat(prelegentExistsInEs).isFalse();

        // Validate the database is empty
        List<Prelegent> prelegentList = prelegentRepository.findAll();
        assertThat(prelegentList).hasSize(databaseSizeBeforeDelete - 1);
    }

    @Test
    @Transactional
    public void searchPrelegent() throws Exception {
        // Initialize the database
        prelegentRepository.saveAndFlush(prelegent);
        prelegentSearchRepository.save(prelegent);

        // Search the prelegent
        restPrelegentMockMvc.perform(get("/api/_search/prelegents?query=id:" + prelegent.getId()))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
            .andExpect(jsonPath("$.[*].id").value(hasItem(prelegent.getId().intValue())))
            .andExpect(jsonPath("$.[*].name").value(hasItem(DEFAULT_NAME.toString())))
            .andExpect(jsonPath("$.[*].bio").value(hasItem(DEFAULT_BIO.toString())))
            .andExpect(jsonPath("$.[*].websiteUrl").value(hasItem(DEFAULT_WEBSITE_URL.toString())));
    }

    @Test
    @Transactional
    public void equalsVerifier() throws Exception {
        TestUtil.equalsVerifier(Prelegent.class);
    }
}

From js perspective we have some end to end test:

'use strict';

describe('Prelegent e2e test', function () {

    var username = element(by.id('username'));
    var password = element(by.id('password'));
    var entityMenu = element(by.id('entity-menu'));
    var accountMenu = element(by.id('account-menu'));
    var login = element(by.id('login'));
    var logout = element(by.id('logout'));

    beforeAll(function () {
        browser.get('/');

        accountMenu.click();
        login.click();

        username.sendKeys('admin');
        password.sendKeys('admin');
        element(by.css('button[type=submit]')).click();
    });

    it('should load Prelegents', function () {
        entityMenu.click();
        element.all(by.css('[ui-sref="prelegent"]')).first().click().then(function() {
            element.all(by.css('h2')).first().getAttribute('data-translate').then(function (value) {
                expect(value).toMatch(/eventsearchApp.prelegent.home.title/);
            });
        });
    });

    it('should load create Prelegent dialog', function () {
        element(by.css('[ui-sref="prelegent.new"]')).click().then(function() {
            element(by.css('h4.modal-title')).getAttribute('data-translate').then(function (value) {
                expect(value).toMatch(/eventsearchApp.prelegent.home.createOrEditLabel/);
            });
            element(by.css('button.close')).click();
        });
    });

    afterAll(function () {
        accountMenu.click();
        logout.click();
    });
});

And js controller test:

'use strict';

describe('Controller Tests', function() {

    describe('Prelegent Management Detail Controller', function() {
        var $scope, $rootScope;
        var MockEntity, MockPreviousState, MockPrelegent;
        var createController;

        beforeEach(inject(function($injector) {
            $rootScope = $injector.get('$rootScope');
            $scope = $rootScope.$new();
            MockEntity = jasmine.createSpy('MockEntity');
            MockPreviousState = jasmine.createSpy('MockPreviousState');
            MockPrelegent = jasmine.createSpy('MockPrelegent');
            

            var locals = {
                '$scope': $scope,
                '$rootScope': $rootScope,
                'entity': MockEntity,
                'previousState': MockPreviousState,
                'Prelegent': MockPrelegent
            };
            createController = function() {
                $injector.get('$controller')("PrelegentDetailController", locals);
            };
        }));


        describe('Root Scope Listening', function() {
            it('Unregisters root scope listener upon scope destruction', function() {
                var eventType = 'eventsearchApp:prelegentUpdate';

                createController();
                expect($rootScope.$$listenerCount[eventType]).toEqual(1);

                $scope.$destroy();
                expect($rootScope.$$listenerCount[eventType]).toBeUndefined();
            });
        });
    });

});

And last, but not least: performance testing in gatling! Really!

import _root_.io.gatling.core.scenario.Simulation
import ch.qos.logback.classic.{Level, LoggerContext}
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import org.slf4j.LoggerFactory

import scala.concurrent.duration._

/**
 * Performance test for the Prelegent entity.
 */
class PrelegentGatlingTest extends Simulation {

    val context: LoggerContext = LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
    // Log all HTTP requests
    //context.getLogger("io.gatling.http").setLevel(Level.valueOf("TRACE"))
    // Log failed HTTP requests
    //context.getLogger("io.gatling.http").setLevel(Level.valueOf("DEBUG"))

    val baseURL = Option(System.getProperty("baseURL")) getOrElse """http://127.0.0.1:8080"""

    val httpConf = http
        .baseURL(baseURL)
        .inferHtmlResources()
        .acceptHeader("*/*")
        .acceptEncodingHeader("gzip, deflate")
        .acceptLanguageHeader("fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3")
        .connectionHeader("keep-alive")
        .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:33.0) Gecko/20100101 Firefox/33.0")

    val headers_http = Map(
        "Accept" -> """application/json"""
    )

    val headers_http_authentication = Map(
        "Content-Type" -> """application/json""",
        "Accept" -> """application/json"""
    )

    val headers_http_authenticated = Map(
        "Accept" -> """application/json""",
        "Authorization" -> "${access_token}"
    )

    val scn = scenario("Test the Prelegent entity")
        .exec(http("First unauthenticated request")
        .get("/api/account")
        .headers(headers_http)
        .check(status.is(401))).exitHereIfFailed
        .pause(10)
        .exec(http("Authentication")
        .post("/api/authenticate")
        .headers(headers_http_authentication)
        .body(StringBody("""{"username":"admin", "password":"admin"}""")).asJSON
        .check(header.get("Authorization").saveAs("access_token"))).exitHereIfFailed
        .pause(1)
        .exec(http("Authenticated request")
        .get("/api/account")
        .headers(headers_http_authenticated)
        .check(status.is(200)))
        .pause(10)
        .repeat(2) {
            exec(http("Get all prelegents")
            .get("/api/prelegents")
            .headers(headers_http_authenticated)
            .check(status.is(200)))
            .pause(10 seconds, 20 seconds)
            .exec(http("Create new prelegent")
            .post("/api/prelegents")
            .headers(headers_http_authenticated)
            .body(StringBody("""{"id":null, "name":"SAMPLE_TEXT", "bio":"SAMPLE_TEXT", "websiteUrl":"SAMPLE_TEXT"}""")).asJSON
            .check(status.is(201))
            .check(headerRegex("Location", "(.*)").saveAs("new_prelegent_url"))).exitHereIfFailed
            .pause(10)
            .repeat(5) {
                exec(http("Get created prelegent")
                .get("${new_prelegent_url}")
                .headers(headers_http_authenticated))
                .pause(10)
            }
            .exec(http("Delete created prelegent")
            .delete("${new_prelegent_url}")
            .headers(headers_http_authenticated))
            .pause(10)
        }

    val users = scenario("Users").exec(scn)

    setUp(
        users.inject(rampUsers(100) over (1 minutes))
    ).protocols(httpConf)
}

All of this is out of the box! How much time do you need to write that logic, tests, make it works and document little bit? Remember, that you can see here entity with only few fields. In target generated application you will have much more code to write. Using JHipster you can save your time and money, and hopefully implement your business logic in that time.

 

 

JHipster entity configuration

Our generator saved us also json file with configuration of generated files structure and relations. You can modify it, extend, add some more json file to next entities and you will have here base configuration of your modular application.

{
    "fluentMethods": true,
    "relationships": [],
    "fields": [
        {
            "fieldName": "name",
            "fieldType": "String",
            "fieldValidateRules": [
                "required"
            ]
        },
        {
            "fieldName": "bio",
            "fieldType": "String"
        },
        {
            "fieldName": "websiteUrl",
            "fieldType": "String"
        }
    ],
    "changelogDate": "20170322233100",
    "dto": "no",
    "service": "no",
    "entityTableName": "prelegent",
    "pagination": "pagination"
}

Liquidbase and i18n

You also have got update in changelog of liquidbase – to remember about database structure. You got configured cache for new entity, internationalizations for JHipster (i18n generated in every language you provided). JHipster gave you 29 files in java, javascript, scala, json, xml and html to faster your work and give you an opportunity for bigger benefits of your work.

 

This entry is quite long because of the code, but most of you won’t be able, have time or opportunity to test it or take a look at github. In this post you gets all JHipster files shown in one place. You can analyse how it looks like, if you would like to work with such code. For me it looks pretty good, so in next blog post we will see how to use JDL and generate big backbone of our application.

 

Paweł Dobrzański

Start-up's fan, technological conferences member, social media enthusiast and low-cost trips traveler.

You may also like

LEAVE A COMMENT

Cześć!

Witaj na moim blogu! Znajdziesz tu proces budowania mojej aplikacji EventSearch za pomocą jhipstera. Podzielę się z Tobą niuansami dot. zakładania i prowadzenia firmy. Mam również nadzieję, że pomogę Ci zaoszczędzić trochę pieniędzy. Zapraszam!

Najpopularniejsze posty

Dzięki, że wpadłeś!

Paweł Dobrzański

Paweł Dobrzański

Start-up's fan, technological conferences member, social media enthusiast and low-cost trips traveler.