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> <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.