1 /* 2 * Copyright (c) 2016 Mockito contributors 3 * This program is made available under the terms of the MIT License. 4 */ 5 package org.mockito.internal.junit; 6 7 import static org.mockito.internal.util.collections.ListUtil.filter; 8 9 import java.util.Collection; 10 import java.util.HashSet; 11 import java.util.LinkedHashMap; 12 import java.util.List; 13 import java.util.Map; 14 import java.util.Set; 15 16 import org.mockito.internal.invocation.finder.AllInvocationsFinder; 17 import org.mockito.internal.stubbing.UnusedStubbingReporting; 18 import org.mockito.internal.util.collections.ListUtil.Filter; 19 import org.mockito.invocation.Invocation; 20 import org.mockito.stubbing.Stubbing; 21 22 /** 23 * Finds unused stubbings 24 */ 25 public class UnusedStubbingsFinder { 26 27 /** 28 * Gets all unused stubbings for given set of mock objects, in order. 29 * Stubbings explicitily marked as LENIENT are not included. 30 */ getUnusedStubbings(Iterable<Object> mocks)31 public UnusedStubbings getUnusedStubbings(Iterable<Object> mocks) { 32 Set<Stubbing> stubbings = AllInvocationsFinder.findStubbings(mocks); 33 34 List<Stubbing> unused = 35 filter( 36 stubbings, 37 new Filter<Stubbing>() { 38 @Override 39 public boolean isOut(Stubbing s) { 40 return !UnusedStubbingReporting.shouldBeReported(s); 41 } 42 }); 43 44 return new UnusedStubbings(unused); 45 } 46 47 /** 48 * Gets unused stubbings per location. This method is less accurate than {@link #getUnusedStubbings(Iterable)}. 49 * It considers that stubbings with the same location (e.g. ClassFile + line number) are the same. 50 * This is not completely accurate because a stubbing declared in a setup or constructor 51 * is created per each test method. Because those are different test methods, 52 * different mocks are created, different 'Invocation' instance is backing the 'Stubbing' instance. 53 * In certain scenarios (detecting unused stubbings by JUnit runner), we need this exact level of accuracy. 54 * Stubbing declared in constructor but realized in % of test methods is considered as 'used' stubbing. 55 * There are high level unit tests that demonstrate this scenario. 56 */ getUnusedStubbingsByLocation(Iterable<Object> mocks)57 public Collection<Invocation> getUnusedStubbingsByLocation(Iterable<Object> mocks) { 58 Set<Stubbing> stubbings = AllInvocationsFinder.findStubbings(mocks); 59 60 // 1st pass, collect all the locations of the stubbings that were used 61 // note that those are _not_ locations where the stubbings was used 62 Set<String> locationsOfUsedStubbings = new HashSet<>(); 63 for (Stubbing s : stubbings) { 64 if (!UnusedStubbingReporting.shouldBeReported(s)) { 65 String location = s.getInvocation().getLocation().toString(); 66 locationsOfUsedStubbings.add(location); 67 } 68 } 69 70 // 2nd pass, collect unused stubbings by location 71 // If the location matches we assume the stubbing was used in at least one test method 72 // Also, using map to deduplicate reported unused stubbings 73 // if unused stubbing appear in the setup method / constructor we don't want to report it 74 // per each test case 75 Map<String, Invocation> out = new LinkedHashMap<>(); 76 for (Stubbing s : stubbings) { 77 String location = s.getInvocation().getLocation().toString(); 78 if (!locationsOfUsedStubbings.contains(location)) { 79 out.put(location, s.getInvocation()); 80 } 81 } 82 83 return out.values(); 84 } 85 } 86