Best Practice Feedback zu Service / JUnit Tests

687b46c0da97

Mitglied
Aloha zusammen,
ich versuche mich gerade an neuen Frameworks (Spring Boot & JUnit). Soweit funktioniert alles und wie immer gibt es auch die ein oder andere Straße nach Rom, weswegen ich gerne einmal Feedback sammeln würde in Bezug auf meine Tests/Implementierung.

Prinzipiell habe ich folgende Architektur im Kopf:


JpaRepository <-- ENTITY --> Service(Impl) <-- DTO/ENTITY --> Frontend/Controller/...

Nun ein Beispiel meines Codes. Prinzipiell sollen in der App verschiedene User, welcher innerhalb einer Company zusammenarbeiten können Ihre Inventur (über Produkte) machen können. Späße wie ACL wollte ich im Frontend machen (z.B. User versucht ein produkt anzulegen für eine andere Company zu welcher er nicht zugehörig ist etc).

Meine Service Klasse:
Java:
@Service
@Transactional
public class ProductService implements IProductService {

    @Autowired
    private ProductRepository productRepository;

    @Autowired
    private ICompanyService companyService;
  
    @Override
    public Page<Product> getProducts(Company company, Pageable pageable) {
        return productRepository.findAllByCompany(company, pageable);
    }

    @Override
    public Product createNewProduct(ProductDto dto) throws Exception {
        Product p = new Product();
        p.setName(dto.getName());
        p.setGtin(dto.getGtin());
        p.setArticleNumber(dto.getArticleNumber());
        p.setCompany(companyService.findById(dto.getCompanyId()).orElseThrow(Exception::new)); //TODO: proper Exception
        p.setPrice(dto.getPrice());
        p.setPurchasingPrice(dto.getPurchasingPrice());

        return productRepository.save(p);
    }
}
Meine Service Test Klasse:
Java:
@DataJpaTest
@ExtendWith(MockitoExtension.class)
class ProductServiceTest {

    @InjectMocks
    private ProductService productService;

    @Mock
    private ProductRepository productRepository;

    @Mock
    private CompanyService companyService;

    private Company company;
    private User user;

    @BeforeEach
    void setup() {
        user = new User();
        user.setEmail("hope@junit-test.de");
        user.setFirstName("Hope");
        user.setLastName("Mikaelson");
        user.setPassword("1234567");

        company = new Company();
        company.setName("BlackRock");
        company.setUser(user);
    }


    @test
    void getProductsForCompanyWhenNoProductsExist() {
        Pageable p = PageRequest.of(0, 5);

        when(productRepository.findAllByCompany(company, p))
                .thenReturn(new PageImpl<>(new ArrayList<>(), p, 0));

        Page<Product> products = productService.getProducts(company, p);

        assertEquals(0, products.getTotalElements());
    }

    @test
    void getProductsForCompany() {
        Pageable p = PageRequest.of(0, 5);
        List<Product> productList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Product product = new Product();
            product.setCompany(company);
            product.setName(String.valueOf(i));
            product.setArticleNumber("P" + i);
            product.setPrice(new BigDecimal("1.99"));
            productList.add(product);
        }

        when(productRepository.findAllByCompany(company, p))
                .thenReturn(new PageImpl<>(productList, p, productList.size()));

        Page<Product> products = productService.getProducts(company, p);

        assertEquals(10, products.getTotalElements());
        assertEquals(2, products.getTotalPages());
        assertEquals(5, products.getSize());
    }

    @test
    void createNewProduct() throws Exception {
        UUID companyUuid = UUID.randomUUID();
        ProductDto dto = new ProductDto();
        dto.setName("MacBook Air M1 2022");
        dto.setPrice(new BigDecimal("1337"));
        dto.setArticleNumber("APL-MBA-M1-22");
        dto.setCompanyId(companyUuid);

        when(companyService.findById(companyUuid)).thenReturn(Optional.ofNullable(company));

        productService.createNewProduct(dto);

        verify(productRepository, times(1)).save(Mockito.any(Product.class));
    }
}

Meine Fragen:
  • Macht die Test implementierung so Sinn? Gäbe es optimierungsbedarf?
  • Macht es in Spring Boot Sinn Tests für die JPARepository Klassen zu schreiben (für die "custom methoden")?
  • Macht der Frontend mix aus DTO/Entity Sinn? Oder sollte der Service Layer nach vorne bereits alles in DTOs übersetzen?
  • Verbesserungsvorschläge?
 
Das klingt für mich komisch. Wenn jemand die API direkt verwendet umgeht er die ACL damit.
Hi! Danke für deinen Einwand, sehr guter Punkt.

Ich habe das einmal umgeschrieben alà
Java:
@Override
public Product createNewProduct(User user, ProductDto dto) {
    if(!user.getCompany().getId().equals(dto.getCompanyId())) {
        throw new AccessDeniedException("Can't create product on company that doesn't belong to user");
    }

    Product p = new Product();
    p.setName(dto.getName());
    p.setGtin(dto.getGtin());
    p.setArticleNumber(dto.getArticleNumber());
    p.setCompany(user.getCompany());
    p.setPrice(dto.getPrice());
    p.setPurchasingPrice(dto.getPurchasingPrice());

    return productRepository.save(p);
}

Macht das Sinn das so zu machen? Generell denke ich, dass ich noch Probleme habe genau zu wissen wann/wo die Beziehungen der objekte aufgesetzt/mantained werden sollen. Falls du andere Beispiele hast (GitHub repos - je komplexer desto besser 😀) oder Präferenzen, lass es mich gerne wissen.
 
Die Prüfung ist sehr einfach, wenn dir das so ausreicht ist es OK. Ich würde etwas bevorzugen, was ich nicht überall separat einbauen muss. Ich will mir nicht jedes mal Gedanken machen müssen, ob etwas falsch oder richtig ist. Ich schreibe z.B.: Annotations zum Validieren, die Annotations setze ich dann im Controller an den Prarameter z.B. ProductDto, dann erfolgt die Prüfung immer bei Übergabe und ich habe im Zwiefelsfall nur eine Stelle zu ändern.

Ich bevorzuge auch weniger Code: https://www.appsdeveloperblog.com/dto-to-entity-and-entity-to-dto-conversion/
 

Neue Themen


Zurück
Oben