diff --git a/concepts/optional-type/.meta/config.json b/concepts/optional-type/.meta/config.json new file mode 100644 index 000000000..074ff8b5e --- /dev/null +++ b/concepts/optional-type/.meta/config.json @@ -0,0 +1,9 @@ +{ + "blurb": "Learn to use the Optional class by helping Tim print details of his company employees.", + "authors": [ + "josealonso" + ], + "contributors": [ + "kahgoh" + ] +} diff --git a/concepts/optional-type/about.md b/concepts/optional-type/about.md new file mode 100644 index 000000000..1890bc981 --- /dev/null +++ b/concepts/optional-type/about.md @@ -0,0 +1,59 @@ +# About the Optional Type + +The **Optional** type was introduced in Java 8 as a way to indicate that a method _may_ return a value. + +Before Java 8, developers had to implement null checks: + +```java +public Employee getEmployee(String name) { + // Assume that getEmployeeByName retrieves an Employee from a database + Employee employee = getEmployeeByName(name); + if (employee != null) { + return employee; + } else { + throw new IllegalArgumentException("Employee not found"); + } +} +``` + +With the Optional API, the code above can be simplified to: + +```java +public Optional getEmployee(String name) { + // Assume that getEmployeeByName returns an Optional + return getEmployeeByName(name) + .orElseThrow(() -> new IllegalArgumentException("Employee not found")); +} +``` + +## Usage with the Stream API + +The Optional API is more useful when many methods are chained and each method returns an Optional object. + +```java +List> employees = new ArrayList<>(); +employees.add(getEmployee("Tim")); +employees.add(getEmployee("Bob")); +employees.add(getEmployee("Alice")); +``` + +```java +public List> getEmployeesInTheirTwenties(){ + return employees.stream() + .filter(Optional::isPresent) + .map(Optional::get) + .filter(employee -> employee.getAge() >= 20 && employee.getAge() < 30) + .collect(Collectors.toList()); +} +``` + +It is important to understand that the Optional API does not eliminate the null checking, +but it defers it until the end of a series of methods, as long as all those methods return an optional object. + +## Recommended usage + +The Optional type is mainly used as returned type. Using it as a parameter type or field type is less common and +not recommended, as explained by one well-known Java language architect in [this SO answer](https://stackoverflow.com/questions/26327957/should-java-8-getters-return-optional-type) + +The official documentation says: +> Optional is primarily intended for use as a method return type where there is a clear need to represent "no result," and where using null is likely to cause errors. A variable whose type is Optional should never itself be null; it should always point to an Optional instance. diff --git a/concepts/optional-type/introduction.md b/concepts/optional-type/introduction.md new file mode 100644 index 000000000..563d42a1b --- /dev/null +++ b/concepts/optional-type/introduction.md @@ -0,0 +1,90 @@ +# Introduction + +## Optional + +The **Optional** type was introduced in Java 8 as a way to indicate that a method _may_ return a value. + +In other words, there is a chance the method returns "no value" at all. + +## Creating an Optional object + +Given an object of type Employee, an Optional object can be created using the static [of][optional-of-javadoc] method: + +```java +Employee employee = new Employee(); +Optional optionalEmployee = Optional.of(employee); +``` + +If the employee _may_ be not present, the static [ofNullable][optional-ofNullable-javadoc] method must be used: + +```java +Employee nullableEmployee = new Employee(); +Optional nullableEmployee = Optional.ofNullable(employee); +``` + +`optionalEmployee` and `nullableEmployee` both are wrappers of an `Employee` object. + +## Basic methods + +If a value is present, the [isPresent][optional-isPresent-javadoc] method returns true and the [get][optional-get-javadoc] method returns the value. + +```java +Employee employee = new Employee("Tim", 45); +Optional optionalEmployee = Optional.ofNullable(employee); +boolean isThereAnEmployee = optionalEmployee.isPresent(); // true +Employee employee = optionalEmployee.get(); +``` + +## Usage + +In order to throw an exception when the value is not present, the [orElseThrow][optional-orElseThrow-javadoc] method must be used. + +```java +public Optional getEmployee(String name) { + // Assume that getEmployeeByName returns an Optional + return getEmployeeByName(name) + .orElseThrow(() -> new IllegalArgumentException("Employee not found")); +} +``` + +If a default value must be returned, the [orElse][optional-orElse-javadoc] method can be used. + +```java +public Optional getEmployee(String name) { + // Assume that getEmployeeByName returns an Optional + return getEmployeeByName(name) + .orElse(new Employee("Daniel")); +} +``` + +Other commonly used method is [ifPresentOrElse][optional-ifPresentOrElse-javadoc], which is used to handle both cases with the same method: the case where the value is present and the case where the value is empty. + +```java +public Optional getEmployee(String name) { + // Assume that getEmployeeByName returns an Optional + return getEmployeeByName(name) + .ifPresentOrElse( + employee -> System.out.println(employee.getName()), + () -> System.out.println("Employee not found") + ); +} +``` + +Provided all the invoked methods return Optional objects, many methods can be chained without having to worry about null checking: + +```java +public Optional getEmployeeAge(String name) { + Optional optionalEmployee = getEmployeeByName(name); + return getEmployeeByName(name) + .map(employee -> employee.getAge()) + .orElse("No employee found"); +} +``` + +[optional-of-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#of(java.lang.Object) +[optional-ofNullable-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#ofNullable(java.lang.Object) +[optional-get-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#get() +[optional-isPresent-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#isPresent() +[optional-orElse-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#orElse(T) +[optional-orElseThrow-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#orElseThrow() +[optional-ifPresentOrElse-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#ifPresentOrElse(java.util.function.Consumer,java.lang.Runnable) diff --git a/concepts/optional-type/links.json b/concepts/optional-type/links.json new file mode 100644 index 000000000..7dd438752 --- /dev/null +++ b/concepts/optional-type/links.json @@ -0,0 +1,2 @@ +[] + diff --git a/config.json b/config.json index a4f248fc7..2881f33ae 100644 --- a/config.json +++ b/config.json @@ -310,6 +310,18 @@ "status": "beta" }, { + "slug": "tim-from-marketing-2", + "name": "tim-from-marketing-2", + "uuid": "a6cfc286-8c62-4f5b-8e59-f6bfc4374092", + "concepts": [ + "optional-type" + ], + "prerequisites": [ + "lists", + "generic-types" + ] + }, + { "slug": "international-calling-connoisseur", "name": "International Calling Connoisseur", "uuid": "03506c5a-601a-42cd-b037-c310208de84d", @@ -947,7 +959,7 @@ "practices": [], "prerequisites": [ "arrays", - "if-statements" + "if-else-statements" ], "difficulty": 5 }, @@ -1971,6 +1983,11 @@ "uuid": "78f3c7b2-cb9c-4d21-8cb4-7106a188f713", "slug": "ternary-operators", "name": "Ternary Operators" + }, + { + "uuid": "", + "slug": "optional-type", + "name": "Optional Type" } ], "key_features": [ diff --git a/exercises/concept/tim-from-marketing-2/.classpath b/exercises/concept/tim-from-marketing-2/.classpath new file mode 100644 index 000000000..a4c8967ba --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/concept/tim-from-marketing-2/.docs/hints.md b/exercises/concept/tim-from-marketing-2/.docs/hints.md new file mode 100644 index 000000000..e53a4fdad --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.docs/hints.md @@ -0,0 +1,10 @@ +# Hints + +## 1. Get an employee by ID + +- This method returns an `Optional` object, not an `Employee` one. + +## 2. Return the name and department of a given employee in a certain format + +- You can call the method `getEmployeeById(int)` to get the employee. +- Remember the syntax of the `ifPresentOrElse()` method. diff --git a/exercises/concept/tim-from-marketing-2/.docs/instructions.md b/exercises/concept/tim-from-marketing-2/.docs/instructions.md new file mode 100644 index 000000000..2690431a2 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.docs/instructions.md @@ -0,0 +1,24 @@ +# Instructions + +In this exercise you will be writing code to retrieve the factory employees. +Employees have an ID, a name and a department name, like in the [tim-from-marketing](/exercises/concept/tim-from-marketing) exercise. +The first field of an employee is always an integer number, but the name and the department name may be empty or null. +The class constructor receives a parameter of type List, which is populated in the tests. +You will be writing two methods: `getEmployeeById(int)` and `getEmployeeDetailsById(int)`. + +## 1. Get an employee by ID + +Implement the `getEmployeeById(int)` method so that it returns an Optional object. + +If the employee does not exist, returns an empty Optional instance. + +## 2. Return the name and department of a given employee in a certain format + +Implement the `getEmployeeDetailsById(int)` method to return a string containing the id, the name and the department of a given employee: + +```java +getEmployeeDetailsById(1) => "1 - Tim - Marketing" +getEmployeeDetailsById(3) => "3 - Steve - Engineering" +``` + +If the employee does not exist or is null, it returns `No employee found for id: [id]`. diff --git a/exercises/concept/tim-from-marketing-2/.docs/introduction.md b/exercises/concept/tim-from-marketing-2/.docs/introduction.md new file mode 100644 index 000000000..563d42a1b --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.docs/introduction.md @@ -0,0 +1,90 @@ +# Introduction + +## Optional + +The **Optional** type was introduced in Java 8 as a way to indicate that a method _may_ return a value. + +In other words, there is a chance the method returns "no value" at all. + +## Creating an Optional object + +Given an object of type Employee, an Optional object can be created using the static [of][optional-of-javadoc] method: + +```java +Employee employee = new Employee(); +Optional optionalEmployee = Optional.of(employee); +``` + +If the employee _may_ be not present, the static [ofNullable][optional-ofNullable-javadoc] method must be used: + +```java +Employee nullableEmployee = new Employee(); +Optional nullableEmployee = Optional.ofNullable(employee); +``` + +`optionalEmployee` and `nullableEmployee` both are wrappers of an `Employee` object. + +## Basic methods + +If a value is present, the [isPresent][optional-isPresent-javadoc] method returns true and the [get][optional-get-javadoc] method returns the value. + +```java +Employee employee = new Employee("Tim", 45); +Optional optionalEmployee = Optional.ofNullable(employee); +boolean isThereAnEmployee = optionalEmployee.isPresent(); // true +Employee employee = optionalEmployee.get(); +``` + +## Usage + +In order to throw an exception when the value is not present, the [orElseThrow][optional-orElseThrow-javadoc] method must be used. + +```java +public Optional getEmployee(String name) { + // Assume that getEmployeeByName returns an Optional + return getEmployeeByName(name) + .orElseThrow(() -> new IllegalArgumentException("Employee not found")); +} +``` + +If a default value must be returned, the [orElse][optional-orElse-javadoc] method can be used. + +```java +public Optional getEmployee(String name) { + // Assume that getEmployeeByName returns an Optional + return getEmployeeByName(name) + .orElse(new Employee("Daniel")); +} +``` + +Other commonly used method is [ifPresentOrElse][optional-ifPresentOrElse-javadoc], which is used to handle both cases with the same method: the case where the value is present and the case where the value is empty. + +```java +public Optional getEmployee(String name) { + // Assume that getEmployeeByName returns an Optional + return getEmployeeByName(name) + .ifPresentOrElse( + employee -> System.out.println(employee.getName()), + () -> System.out.println("Employee not found") + ); +} +``` + +Provided all the invoked methods return Optional objects, many methods can be chained without having to worry about null checking: + +```java +public Optional getEmployeeAge(String name) { + Optional optionalEmployee = getEmployeeByName(name); + return getEmployeeByName(name) + .map(employee -> employee.getAge()) + .orElse("No employee found"); +} +``` + +[optional-of-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#of(java.lang.Object) +[optional-ofNullable-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#ofNullable(java.lang.Object) +[optional-get-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#get() +[optional-isPresent-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#isPresent() +[optional-orElse-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#orElse(T) +[optional-orElseThrow-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#orElseThrow() +[optional-ifPresentOrElse-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#ifPresentOrElse(java.util.function.Consumer,java.lang.Runnable) diff --git a/exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl b/exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl new file mode 100644 index 000000000..fab1aaf25 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl @@ -0,0 +1,3 @@ +# Introduction + +%{concept:optional-types} diff --git a/exercises/concept/tim-from-marketing-2/.meta/config.json b/exercises/concept/tim-from-marketing-2/.meta/config.json new file mode 100644 index 000000000..e65124bc0 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.meta/config.json @@ -0,0 +1,26 @@ +{ + "name": "tim-from-marketing-2", + "uuid": "a6cfc286-8c62-4f5b-8e59-f6bfc4374092", + "concepts": [ + "optional-type" + ], + "authors": [ + "josealonso" + ], + "contributors": [ + "kahgoh" + ], + "files": { + "solution": [ + "src/main/java/EmployeeDatabase.java" + ], + "test": [ + "src/test/java/EmployeeDatabaseTest.java" + ], + "exemplar": [ + ".meta/src/reference/java/EmployeeDatabase.java" + ] + }, + "icon": "nullability", + "blurb": "Learn to use the Optional class by helping Tim print details of his company employees." +} diff --git a/exercises/concept/tim-from-marketing-2/.meta/design.md b/exercises/concept/tim-from-marketing-2/.meta/design.md new file mode 100644 index 000000000..911c913ae --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.meta/design.md @@ -0,0 +1,45 @@ +# Design + +## Goal + +The goal of this exercise is to teach the student how to use the Optional API. +We will use the most common methods: `ifPresent`, `orElse`, `ifPresentOrElse`, `orElseThrow`. +The `isPresent` and `get` methods are not presented, since they do not provide any value over an ordinary null check. + +## Learning objectives + +- Know what optional types are. +- Know how to use Optional fields. +- Know how to use methods that return an Optional type. +- See the utility of some Stream methods, `flatMap` specifically. + +## Out of scope + +- Streams API. + +## Concepts + +This Concepts Exercise's Concepts are: + +- `Optional` class and some methods that mimic a null check. + +## Prerequisites + +This Concept Exercise's prerequisites Concepts are: + +- `custom-classes`. +- `lists`. +- `generic-types`. + +## Analyzer + +This exercise could benefit from the following rules in the [analyzer]: + +- `essential`: If the solution uses `null` in any method, encourage the student to use `Optional` instead. +- `actionable`: If the solution uses the `get` or `isPresent` methods of the Optional API, encourage the student to use `orElse`, `orElseThrow` or `ifPresentOrElse` instead. +- `informative`: TODO. + +If the solution does not receive any of the above feedback, it must be exemplar. +Leave a `celebratory` comment to celebrate the success! + +[analyzer]: https://github.com/exercism/java-analyzer diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java new file mode 100644 index 000000000..f43bf8d42 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java @@ -0,0 +1,58 @@ +import java.util.Objects; + +/** + * Holds information about an employee. There is no need to change this file. +*/ + +class Employee { + private final int id; + private final String name; + private final String department; + + public Employee(int id, String name, String department) { + this.id = id; + this.name = name; + this.department = department; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDepartment() { + return department; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Employee employee = (Employee) o; + return id == employee.id && Objects.equals(name, employee.name) + && Objects.equals(department, employee.department); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, department); + } + + @Override + public String toString() { + return "Employee{" + + "id=" + id + + ", name='" + name + '\'' + + ", department='" + department + '\'' + + '}'; + } + +} + diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeDatabase.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeDatabase.java new file mode 100644 index 000000000..0b1eebf4a --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeDatabase.java @@ -0,0 +1,48 @@ +import java.util.List; +import java.util.Optional; +import java.util.Objects; + +class EmployeeDatabase { + + // This list is populated in the tests + private List employeesList; + + public EmployeeDatabase(List listOfEmployees) { + employeesList = listOfEmployees; + } + + public Optional getEmployeeById(int employeeId) { + for (Employee employee : employeesList) { + if (Objects.equals(employee.getId(), employeeId)) { + return Optional.of(employee); + } + } + return Optional.empty(); + } + + public String getEmployeeDetailsById(int employeeId) { + Optional nullableEmployee = getEmployeeById(employeeId); + StringBuilder stringBuilder = new StringBuilder(); + nullableEmployee.ifPresentOrElse( + employee1 -> { + Optional.ofNullable(employee1.getName()) + .ifPresentOrElse(name -> stringBuilder + .append(employeeId).append(" - ") + .append(employee1.getName()).append(" - ") + .append(employee1.getDepartment()), + () -> { + stringBuilder.append("No employee found for id: ").append(employeeId); + }); + }, + () -> { + stringBuilder.append("That id does not exist: ").append(employeeId); + }); + return stringBuilder.toString(); + } +} + + + + + + diff --git a/exercises/concept/tim-from-marketing-2/.project b/exercises/concept/tim-from-marketing-2/.project new file mode 100644 index 000000000..8fe777e8e --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.project @@ -0,0 +1,34 @@ + + + tim-from-marketing-2 + Project tim-from-marketing-2 created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + + 1739322795991 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.buildship.core.prefs b/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 000000000..62e3e7e80 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir=../.. +eclipse.preferences.version=1 diff --git a/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.jdt.core.prefs b/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..bbf9f297a --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.classpath.outputOverlappingAnotherSource=ignore +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.source=17 diff --git a/exercises/concept/tim-from-marketing-2/build.gradle b/exercises/concept/tim-from-marketing-2/build.gradle new file mode 100644 index 000000000..d28f35dee --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/build.gradle @@ -0,0 +1,25 @@ +plugins { + id "java" +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform("org.junit:junit-bom:5.10.0") + testImplementation "org.junit.jupiter:junit-jupiter" + testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" +} + +test { + useJUnitPlatform() + + testLogging { + exceptionFormat = "full" + showStandardStreams = true + events = ["passed", "failed", "skipped"] + } +} diff --git a/exercises/concept/tim-from-marketing-2/src/main/java/Employee.java b/exercises/concept/tim-from-marketing-2/src/main/java/Employee.java new file mode 100644 index 000000000..69fc246f6 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/src/main/java/Employee.java @@ -0,0 +1,48 @@ +import java.util.Objects; + +class Employee { + private final int id; + private final String name; + private final String department; + + public Employee(int id, String name, String department) { + this.id = id; + this.name = name; + this.department = department; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDepartment() { + return department; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Employee employee = (Employee) o; + return id == employee.id && + Objects.equals(name, employee.name) && Objects.equals(department, employee.department); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, department); + } + + @Override + public String toString() { + return "Employee{" + + "id=" + id + + ", name='" + name + '\'' + + ", department='" + department + '\'' + + '}'; + } +} diff --git a/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeDatabase.java b/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeDatabase.java new file mode 100644 index 000000000..2ed4e480f --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeDatabase.java @@ -0,0 +1,50 @@ +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +class EmployeeDatabase { + + private List employeesList; + + public EmployeeDatabase(List listOfEmployees) { + // This list is populated in the tests + employeesList = listOfEmployees; + } + + public Optional getEmployeeById(int employeeId) { + for (Employee employee : employeesList) { + if (Objects.equals(employee.getId(), employeeId)) { + return Optional.of(employee); + } + } + return Optional.empty(); + } + + public String getEmployeeDetailsById(int employeeId) { + Optional nullableEmployee = getEmployeeById(employeeId); + StringBuilder stringBuilder = new StringBuilder(); + nullableEmployee.ifPresentOrElse( + employee1 -> { + Optional.ofNullable(employee1.getName()) + .ifPresentOrElse(name -> stringBuilder.append(employeeId).append(" - ") + .append(employee1.getName()).append(" - ") + .append(employee1.getDepartment()), + () -> { + stringBuilder.append("No employee found for id: ").append(employeeId); + }); + }, + () -> { + stringBuilder.append("No employee found for id: ").append(employeeId); + }); + return stringBuilder.toString(); + } + +// public Optional getEmployeeById(int employeeId) { +// throw new UnsupportedOperationException("Please implement this method"); +// } +// +// public String getEmployeeDetailsById(int employeeId) { +// throw new UnsupportedOperationException("Please implement this method"); +// } + +} diff --git a/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeDatabaseTest.java b/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeDatabaseTest.java new file mode 100644 index 000000000..ecef40352 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeDatabaseTest.java @@ -0,0 +1,102 @@ +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.ArrayList; +import java.util.Optional; + +public class EmployeeDatabaseTest { + + private EmployeeDatabase employeeDatabase; + private List listOfEmployees = new ArrayList<>(); + + void initList() { + listOfEmployees.add(new Employee(1, "Tim", "Marketing")); + listOfEmployees.add(new Employee(2, "Joe", "Sales")); + listOfEmployees.add(new Employee(3, "Jane", "IT")); + listOfEmployees.add(new Employee(4, null, null)); + listOfEmployees.add(new Employee(5, null, "IT")); + listOfEmployees.add(new Employee(6, "Alice", null)); + } + + @BeforeEach + void setup() { + initList(); + employeeDatabase = new EmployeeDatabase(listOfEmployees); + } + + @Test + @Tag("task:1") + @DisplayName("Retrieve one employee by id") + void retrieveOneEmployeeById() { + assertThat(employeeDatabase.getEmployeeById(2)) + .isEqualTo(Optional.of(listOfEmployees.get(1))); + } + + @Test + @Tag("task:2") + @DisplayName("Retrieve other employee by id") + void retrieveOtherEmployeeById() { + assertThat(employeeDatabase.getEmployeeById(3)) + .isEqualTo(Optional.of(listOfEmployees.get(2))); + } + + @Test + @Tag("task:3") + @DisplayName("Retrieve employee by id when some fields are null") + void retrieveEmployeeById_withNullFields() { + assertThat(employeeDatabase.getEmployeeById(4)) + .isEqualTo(Optional.of(listOfEmployees.get(3))); + } + + @Test + @Tag("task:4") + @DisplayName("Retrieve employee by id when the id does not exist") + void retrieveEmployeeById_forANonExistingId() { + assertThat(employeeDatabase.getEmployeeById(7)) + .isEqualTo(Optional.empty()); + } + + @Test + @Tag("task:5") + @DisplayName("Retrieve employee details by id") + void retrieveEmployeeDetailsById() { + assertThat(employeeDatabase.getEmployeeDetailsById(2)) + .isEqualTo("2 - Joe - Sales"); + } + + @Test + @Tag("task:6") + @DisplayName("Retrieve employee details by id when some fields are null") + void retrieveEmployeeDetailsById_withNullFields() { + assertThat(employeeDatabase.getEmployeeDetailsById(4)) + .isEqualTo("No employee found for id: 4"); + } + + @Test + @Tag("task:7") + @DisplayName("Retrieve employee details by id when name is null and department is not null") + void retrieveEmployeeDetailsById_whenNameIsNull() { + assertThat(employeeDatabase.getEmployeeDetailsById(5)) + .isEqualTo("No employee found for id: 5"); + } + + @Test + @Tag("task:8") + @DisplayName("Retrieve employee details by id when department is null and name is not null") + void retrieveEmployeeDetailsById_whenDepartmentIsNull() { + assertThat(employeeDatabase.getEmployeeDetailsById(6)) + .isEqualTo("No employee found for id: 6"); + } + + @Test + @Tag("task:9") + @DisplayName("Retrieve employee details by id when the id does not exist") + void retrieveEmployeeDetailsById_forANonExistingId() { + assertThat(employeeDatabase.getEmployeeDetailsById(10)) + .isEqualTo("No employee found for id: 10"); + } +} diff --git a/exercises/settings.gradle b/exercises/settings.gradle index 470f7aba7..82100309a 100644 --- a/exercises/settings.gradle +++ b/exercises/settings.gradle @@ -22,6 +22,7 @@ include 'concept:salary-calculator' include 'concept:secrets' include 'concept:squeaky-clean' include 'concept:tim-from-marketing' +include 'concept:tim-from-marketing-2' include 'concept:wizards-and-warriors' include 'concept:wizards-and-warriors-2'