Skip to content
mutastic

Java edition

Optional

Operators that change how an Optional is made, checked and unwrapped. Together they ask whether your tests have both a value and no value wherever the code tells them apart.

OperatorChangesSet
optional/swap-presentisPresent and isEmptyextended
optional/emptyOptional.of(x) to Optional.empty()extended
optional/remove-filterOptional.filter, removedextended
optional/always-defaultopt.orElse(x) to xextended
optional/no-defaultopt.orElse(x) to opt.get()extended

Each example is a class in samples/operator-docs with a test that misses its mutant, and each card is what mutastic run samples/operator-docs --operators=all prints for it. Run these with --operators=defaults,extended.

optional/swap-present, in the extended set. isPresent and isEmpty swap places, on Optional and its primitive forms.

The bug it stands for: a check the wrong way round, so that the code handles a missing value as if it were there. It costs on the path a test rarely tries: the customer who isn’t found, the setting that isn’t set.

/// What a profile shows: the nickname if there is one, and whether it's a nickname.
public record Badge(String shown, boolean nickname) {}
public static Badge badge(String name, Optional<String> nickname) {
return new Badge(nickname.orElse(name), nickname.isPresent());
}

The test checks the name each badge shows, with a nickname and without, but never whether it says it’s a nickname:

@Test
void theNicknameIsShown() {
assertEquals("Annie", Badges.badge("Ann", Optional.of("Annie")).shown());
assertEquals("Ann", Badges.badge("Ann", Optional.empty()).shown());
}
Terminal window
✗ MISSED no test tells isPresent from isEmpty in badge()
┌─ src/main/java/com/example/optional/Badges.java:10
│
9 │ public static Badge badge(String name, Optional<String> nickname) {
10 │ return new Badge(nickname.orElse(name), nickname.isPresent());
│ ^^^^^^^^^ isPresent changed to isEmpty
11 │ }
│
= mutant - return new Badge(nickname.orElse(name), nickname.isPresent());
+ return new Badge(nickname.orElse(name), nickname.isEmpty());
= note: covered by BadgesTest#theNicknameIsShown (its 1 covering test still passes)
= help: add a test with an empty Optional and one with a value, and assert the result of each
done when mutastic verify m82cb842a samples/operator-docs passes
reproduce mutastic run --only=m82cb842a samples/operator-docs
or ignore mutastic ignore m82cb842a --reason="<why>" samples/operator-docs

The test that catches it: one with a value and one without, asserting what each does.

@Test
void aNicknameIsMarkedAsOne() {
assertTrue(Badges.badge("Ann", Optional.of("Annie")).nickname());
assertFalse(Badges.badge("Ann", Optional.empty()).nickname());
}

Edition notes: isEmpty came in Java 11: in a class file for an older release the mutant is skipped as unviable:, as the method may not be there, and a missing method would look like a catch. The Rust edition has this operator too, optional/swap-present, in stronger, for is_some and is_none, and is_ok and is_err, since Option and Result are everyday Rust rather than a modern API.

optional/empty, in the extended set. An Optional made with a value is empty instead: Optional.of(x) and ofNullable(x) become Optional.empty(), and x is still worked out.

The bug it stands for: code that gives back nothing where it should give a value, which callers treat as “not found” without complaint. It costs in lookups and settings: an empty result looks just like a real absence, so a test that only checks the call doesn’t fail still passes.

/// The chosen page size, or the standard one when none is chosen.
public int size(Integer chosen) {
Optional<Integer> custom = Optional.ofNullable(chosen);
return custom.orElse(standard);
}

The test’s chosen size is the standard one, so it comes out the same whether or not the choice is kept. A choice of null gives the same answer as well, so the card holds both:

@Test
void aChosenSize() {
assertEquals(20, new Pages(20).size(20));
}
Terminal window
✗ MISSED no test depends on the value of chosen in size() · m528413d3
┌─ src/main/java/com/example/optional/Pages.java:14
│
13 │ public int size(Integer chosen) {
14 │ Optional<Integer> custom = Optional.ofNullable(chosen);
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ parameter is null
15 │ return custom.orElse(standard);
│
= change chosen is null from the start of the method (not shown in the source)
= also ✗ no test checks the value in the Optional made by ofNullable(…) in size() · m02d5e708
+ Optional<Integer> custom = Optional.empty();
= note: covered by PagesTest#aChosenSize, PagesTest#theStandardSize (all 2 covering tests still pass)
= help: add a test whose result depends on chosen, and assert it
// in PagesTest
@Test
void sizeWithChosen0ForStandard20() {
var pages = new Pages(20); // as PagesTest#aChosenSize builds it
assertEquals(0, pages.size(0)); // today 0 · with m528413d3 20
}
check today's value is the one you intend: the test pins what the code does now
done when mutastic verify m528413d3,m02d5e708 samples/operator-docs passes
reproduce mutastic run --only=m528413d3,m02d5e708 samples/operator-docs
or ignore mutastic ignore <id> --reason="<why>" samples/operator-docs for each of m528413d3, m02d5e708

The test that catches it: one that gets the value out, with a value unlike the default.

@Test
void aChosenSizeIsKept() {
assertEquals(50, new Pages(20).size(50));
}

Edition notes: the Rust edition has this operator too, optional/empty, in stronger: Some(x) becomes None, where it isn’t returned.

optional/remove-filter, in the extended set. Optional.filter is removed, so the value goes on whatever the predicate says.

The bug it stands for: a check that’s missing, or wrong, in a chain: the value is used when it shouldn’t be. It costs in rules that hide in one call: only an active customer, only a discount that’s still valid, only a nickname the rules allow.

/// The nickname asked for, if the rules allow it.
public Optional<String> claim(Optional<String> asked) {
return asked.filter(allowed);
}

The test only asks for a nickname the rules allow:

@Test
void anAllowedNickname() {
var nicknames = new Nicknames(nickname -> !nickname.equals("admin"));
assertEquals(Optional.of("annie"), nicknames.claim(Optional.of("annie")));
}
Terminal window
✗ MISSED no test has a value .filter(…) must turn away in claim()
┌─ src/main/java/com/example/optional/Nicknames.java:15
│
14 │ public Optional<String> claim(Optional<String> asked) {
15 │ return asked.filter(allowed);
│ ^^^^^^ filter removed
16 │ }
│
= mutant - return asked.filter(allowed);
+ return asked;
= note: covered by NicknamesTest#anAllowedNickname (its 1 covering test still passes)
= help: add a test with a value the filter rejects, and assert the Optional comes out empty
done when mutastic verify m34668ba0 samples/operator-docs passes
reproduce mutastic run --only=m34668ba0 samples/operator-docs
or ignore mutastic ignore m34668ba0 --reason="<why>" samples/operator-docs

The test that catches it: one with a value the filter must turn away.

@Test
void aNicknameTheRulesForbidIsRefused() {
var nicknames = new Nicknames(nickname -> !nickname.equals("admin"));
assertEquals(Optional.empty(), nicknames.claim(Optional.of("admin")));
}

Edition notes: the Rust edition has this operator too, optional/remove-filter, in extended, for opt.filter(p) where opt isn’t visibly an iterator.

optional/always-default and optional/no-default, in the extended set. Each takes away one side of orElse. opt.orElse(x) becomes x, so the value in the Optional is never used. opt.orElse(x) and opt.orElseGet(s) become opt.get(), so an empty Optional throws.

The bug it stands for: a default that’s always used, so the configured value is ignored; or no default at all, so a missing value throws. It costs in configuration: a setting that does nothing until someone’s production value matters, or a start-up failure when it’s left out.

public int port(Optional<Integer> configured) {
return configured.orElse(defaultPort);
}

The test configures the default port, so whether the configured value is used never shows:

@Test
void theConfiguredPort() {
assertEquals(8080, new Server(8080).port(Optional.of(8080)));
}
Terminal window
✗ MISSED no test needs the value in the Optional before .orElse(…) in port()
┌─ src/main/java/com/example/optional/Server.java:13
│
12 │ public int port(Optional<Integer> configured) {
13 │ return configured.orElse(defaultPort);
│ ^^^^^^ now always the default
14 │ }
│
= mutant - return configured.orElse(defaultPort);
+ return defaultPort;
= note: covered by ServerTest#theConfiguredPort, ServerTest#theDefaultPort (all 2 covering tests still pass)
= help: add a test where the Optional has a value other than the default, and assert it comes back
done when mutastic verify md962bd31 samples/operator-docs passes
reproduce mutastic run --only=md962bd31 samples/operator-docs
or ignore mutastic ignore md962bd31 --reason="<why>" samples/operator-docs

The other side: a test that always has a value never needs the default.

/// The largest of `values`, or `ifNone` when there are none.
public static int largest(List<Integer> values, int ifNone) {
return values.stream().max(Integer::compare).orElse(ifNone);
}
@Test
void theLargest() {
assertEquals(9, Numbers.largest(List.of(3, 9), 0));
}
Terminal window
✗ MISSED no test has an empty Optional reach .orElse(…) in largest()
┌─ src/main/java/com/example/optional/Numbers.java:8
│
7 │ public static int largest(List<Integer> values, int ifNone) {
8 │ return values.stream().max(Integer::compare).orElse(ifNone);
│ ^^^^^^ throws when empty
9 │ }
│
= mutant - return values.stream().max(Integer::compare).orElse(ifNone);
+ return values.stream().max(Integer::compare).get();
= note: inputs seen: ifNone = 0
covered by NumbersTest#theLargest (its 1 covering test still passes)
= help: add a test where the Optional is empty, and assert the default comes back
done when mutastic verify m232b5b62 samples/operator-docs passes
reproduce mutastic run --only=m232b5b62 samples/operator-docs
or ignore mutastic ignore m232b5b62 --reason="<why>" samples/operator-docs

The test that catches it: two tests, one for each side: a value that isn’t the default, and an empty Optional.

@Test
void aConfiguredPortIsUsed() {
assertEquals(9090, new Server(8080).port(Optional.of(9090)));
}
@Test
void noValuesGiveTheFallback() {
assertEquals(0, Numbers.largest(List.of(), 0));
}

Edition notes: removing opt.ifPresent(…) is remove void call’s change, as it returns nothing. orElseThrow’s supplier is left alone: which exception is thrown is rarely worth a test. The Rust edition has both operators too, optional/always-default and optional/no-default, in stronger, for unwrap_or, unwrap_or_else, unwrap_or_default and map_or.