function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Meggan LandisMeggan Landis 

How to write apex class test for working trigger - to create error message when date/time for events overlap?

Hi all-
In full disclosure, I am an administrator, not a developer, but I am trying to work my way through this problem. My users want to see an error message when they try to create an event when one is already scheduled on their calendar during that date/time. I did enough research to understand that I needed to create an Apex Trigger and created the trigger below in my sandbox (and, again, in full disclosure, I pulled this together from a site I found). It does work in the sandbox. When I uploaded and tried to validate in production, I learned I needed to also create the apex class test to accompany this trigger. I have read through a trailhead so I understand the basics, but I don't know how to create a test with date/time. That's where I'm stuck. Can anyone help with how I would create a test for this trigger? I'm a bit lost at this point. Thank you!!!

trigger Conflict on Event (before insert, before update) {
String thisId; //an event will always overlap with itself so omit it from the query
String resource; //only treat events as conflicts when they share the same resource
Datetime startDate;
Datetime endDate;
for (Event newEvent : trigger.new){
thisId = newEvent.Id;
resource = newEvent.OwnerId;
startDate = newEvent.StartDateTime;
endDate = newEvent.EndDateTime;
List<Event> events = [SELECT Id FROM Event WHERE EndDateTime >= :startDate AND StartDateTime <= :endDate AND OwnerId = :resource AND ID != :thisId];
if (!events.isEmpty()) {
newEvent.Conflict__c = 'CONFLICT';
} else {
newEvent.Conflict__c = '';
}
}
}
Andrew GAndrew G
Hi Meggan

Firstly, not a bad attempt at a first trigger.  The main issue is the SELECT statements inside FOR loops is a no-no - if a bulk update occurs, you will  more than likely get a Too Many SOQLs.
So how to solve ?
Since we using the owner as part of the criteria for the duplcate event, lets query by Owner Id.  So get a collection of Owner Ids for all the events in the trigger - so that the trigger is bulkified. (capable of handling a bulk update)  
Query the events using those Owner Ids.
Then create a Map of all events by Owner Id (Map<Id, List<Event>>)
then in the Map, if there is no owner key, then it is an insert of a new event.  (remember that query is before insert, so the new event does not exist)
if we find the Owner Id in the keyset, there are existing events for the Owner, so loop those events and test for the datetime value and throw an error is there is an issue.
So a revised trigger:
trigger Conflict on Event (before insert, before update) {
	String thisId;		//an event will always overlap with itself so omit it from the query
	String resource; 	//only treat events as conflicts when they share the same resource
	Datetime startDate;
	Datetime endDate;

	List<Id> ownerIds = new List<Id>();
	for (Event newEvent : trigger.new){
		ownerIds.add(newEvent.OwnerId);
	}

	List<Event> events = [SELECT Id, StartDateTime, EndDateTime, OwnerId FROM Event WHERE OwnerId IN :ownerIds];
	Map<Id, List<Event>> eventsByResource = new Map<Id,List<Event>>();
	for (Event e : events ) {
		if (e.OwnerId != null) {
			List<Event> tempList = eventsByResource.get(e.OwnerId);
			if (tempList == null) {
				tempList = new List<Event>();
			}
			tempList.add(e);
			eventsByResource.put(e.OwnerId, tempList);
		}
	}

	for (Event newEvent : trigger.new){
		resource 	= newEvent.OwnerId;
		thisId 		= newEvent.Id;
		startDate 	= newEvent.StartDateTime;
		endDate 	= newEvent.EndDateTime;

		if (eventsByResource.containsKey(resource)) {
			List<Event> eventList = eventsByResource.get(resource);
			for (Event e : eventList ) {
				if (e.Id == thisId) {
					//do nothing 
				} else if( startDate <= e.EndDateTime && endDate < e.StartDateTime ) {
					Trigger.new[0].adderror('Duplicate booking');		
				}
			}
		}
	}
}

Now the test class
the two concerns would be how to set a date time to the event test - we would use Datetime.newInstance(2019, 06, 21, 11, 00, 0);
Second one, how to capture that we are throwing an exception.For that we do are system asserts in the Catch section of the test data.

Now, I am a bit over the top with test data, but below is a test class that I would use.
Note that I set up some test data at the start of the test class, @testSetup , which saves me re-creating those records that are common to several tests, in this case 2 x user records and a single event for one of the users.  Test data setup in this manner will "reset" after each test method completes, so they can be edited and will revert to their original state ready for the next method.  I also use a small method in my test class to create my user records.  That would normally sit in a Test Helper Class.

Note that I run a separate test method for each test that I want to do, including a negative test (where the error message will not fire when there is no overlap) and a bulk insert test.
@isTest
private class ConflictOnEventTriggerTest {
	
	@testSetup static void setup() {
		// Create 2 x user records
		User user1 = createUser('user@One.net', 'userOne','test@none.net','userOne');
		Insert user1;
		User user2 = createUser('user@Two.net', 'userTwo','test@none.net','userTwo');
		Insert user2;

		Event e = new Event();
		e.StartDateTime = Datetime.newInstance(2019, 06, 21, 10, 00, 0);
		e.EndDateTime	= Datetime.newInstance(2019, 06, 21, 11, 00, 0);
		e.OwnerId = user1.Id;
		Insert e;

	}

	@isTest static void test_event_insert_start_overlap() {
		// Implement test code
		User user1 = [SELECT Id FROM User WHERE LastName = 'userOne'];

		Event e = new Event();
		e.StartDateTime = Datetime.newInstance(2019, 06, 21, 10, 30, 0);
		e.EndDateTime	= Datetime.newInstance(2019, 06, 21, 11, 30, 0);
		e.OwnerId = user1.Id;

		try {
			insert e;
		} catch (Exception ex) {
			Boolean expectedExceptionThrown = ex.getMessage().contains('Duplicate booking') ? true : false;
			System.assertEquals(true, expectedExceptionThrown);
		}

	}
	
	@isTest static void test_event_insert_end_overlap() {
		// Implement test code
		User user1 = [SELECT Id FROM User WHERE LastName = 'userOne'];

		Event e = new Event();
		e.StartDateTime = Datetime.newInstance(2019, 06, 21, 09, 30, 0);
		e.EndDateTime	= Datetime.newInstance(2019, 06, 21, 10, 30, 0);
		e.OwnerId = user1.Id;

		try {
			insert e;
		} catch (Exception ex) {
			Boolean expectedExceptionThrown = ex.getMessage().contains('Duplicate booking') ? true : false;
			System.assertEquals(true, expectedExceptionThrown);
		}

	}

	@isTest static void test_event_insert_both_overlap() {
		// Implement test code
		User user1 = [SELECT Id FROM User WHERE LastName = 'userOne'];

		Event e = new Event();
		e.StartDateTime = Datetime.newInstance(2019, 06, 21, 10, 15, 0);
		e.EndDateTime	= Datetime.newInstance(2019, 06, 21, 10, 45, 0);
		e.OwnerId = user1.Id;

		try {
			insert e;
		} catch (Exception ex) {
			Boolean expectedExceptionThrown = ex.getMessage().contains('Duplicate booking') ? true : false;
			System.assertEquals(true, expectedExceptionThrown);
		}

	}

	@isTest static void test_event_insert_both_encompass() {
		// Implement test code
		User user1 = [SELECT Id FROM User WHERE LastName = 'userOne'];

		Event e = new Event();
		e.StartDateTime = Datetime.newInstance(2019, 06, 21, 09, 15, 0);
		e.EndDateTime	= Datetime.newInstance(2019, 06, 21, 11, 45, 0);
		e.OwnerId = user1.Id;

		try {
			insert e;
		} catch (Exception ex) {
			Boolean expectedExceptionThrown = ex.getMessage().contains('Duplicate booking') ? true : false;
			System.assertEquals(true, expectedExceptionThrown);
		}

	}

	@isTest static void test_event_update() {
		// Implement test code
		User user1 = [SELECT Id FROM User WHERE LastName = 'userOne'];

		// Implement test code
		Event existingEvent = [SELECT Id, StartDateTime, EndDateTime FROM Event WHERE OwnerId = :user1.Id];
		existingEvent.EndDateTime = Datetime.newInstance(2019, 06, 21, 11, 45, 0);
		try {
			update existingEvent;
		} catch (Exception ex) {
			//should not be an exception
			System.debug('The following exception has occurred: ' + ex.getMessage());
			Boolean expectedExceptionThrown = ex.getMessage().contains('Duplicate booking') ? true : false;
			System.assertEquals(true, expectedExceptionThrown);
		}
		//grab the event and check that the time updated without error
		existingEvent = [SELECT Id, StartDateTime, EndDateTime FROM Event WHERE OwnerId = :user1.Id];
		Datetime expectedDateTime = Datetime.newInstance(2019, 06, 21, 11, 45, 0);
		System.assertEquals(expectedDateTime,existingEvent.EndDateTime);
	}

	@isTest static void test_event_insert_differentUser() {
		// Implement test code
		User user1 = [SELECT Id FROM User WHERE LastName = 'userOne'];
		User user2 = [SELECT Id FROM User WHERE LastName = 'userTwo'];
		//create an event for user two with same times as original test event
		Event e = new Event();
		e.StartDateTime = Datetime.newInstance(2019, 06, 21, 10, 00, 0);
		e.EndDateTime	= Datetime.newInstance(2019, 06, 21, 11, 00, 0);
		e.OwnerId = user2.Id;

		try {
			insert e;
		} catch (Exception ex) {
			//should not be an exception
			System.debug('The following exception has occurred: ' + ex.getMessage());
		}
		//grab the event and check that the time updated without error
		Event userOneEvent = [SELECT Id, StartDateTime, EndDateTime FROM Event WHERE OwnerId = :user1.Id];
		Event userTwoEvent = [SELECT Id, StartDateTime, EndDateTime FROM Event WHERE OwnerId = :user2.Id];
		System.assertEquals(userOneEvent.EndDateTime,userTwoEvent.EndDateTime);

	}

	@isTest static void test_event_insert_no_overlap() {
		// Implement test code
		User user1 = [SELECT Id FROM User WHERE LastName = 'userOne'];

		Event e = new Event();
		e.StartDateTime = Datetime.newInstance(2019, 06, 21, 11, 00, 0);
		e.EndDateTime	= Datetime.newInstance(2019, 06, 21, 11, 30, 0);
		e.OwnerId = user1.Id;

		try {
			insert e;
		} catch (Exception ex) {
			//should not be an exception
			System.debug('The following exception has occurred: ' + ex.getMessage());

		}
		List<Event> userOneEvents = [SELECT Id, StartDateTime, EndDateTime FROM Event WHERE OwnerId = :user1.Id];
		System.assertEquals(2, userOneEvents.size());

	}

	@isTest static void test_event_insert_bulk_no_overlap() {
		// Implement test code
		User user2 = [SELECT Id FROM User WHERE LastName = 'userTwo'];
		
		List<Event> eventList = new List<Event>();
		for (Integer i = 0; i<200; i++){
			Event e = new Event();
			e.StartDateTime = Datetime.newInstance(2019, 06, 21, 11, 00, i);
			e.EndDateTime	= Datetime.newInstance(2019, 06, 21, 11, 30, i+1);
			e.OwnerId = user2.Id;
			eventList.add(e);
		}

		try {
			insert eventList;
		} catch (Exception ex) {
			//should not be an exception
			System.debug('The following exception has occurred: ' + ex.getMessage());

		}
		List<Event> userOneEvents = [SELECT Id, StartDateTime, EndDateTime FROM Event WHERE OwnerId = :user2.Id];
		System.assertEquals(200, userOneEvents.size()); 
	}


	public static User createUser(String uName, String lName, String eAdd, String alias) {
		User user = new User();
		user.Username = uName;
		user.LastName = lName;
		user.Email = eAdd;
		user.Alias = alias;
		user.TimeZoneSidKey = 'America/New_York';
		user.LocaleSidKey = 'en_US';
		user.EmailEncodingKey = 'ISO-8859-1';
		user.ProfileId = [SELECT id FROM Profile WHERE Name='Standard User'].Id;
		user.LanguageLocaleKey = 'en_US';
		return user;
	}

}

Hope the above helps and is relatively clear.

Regards

Andrew



 
Meggan LandisMeggan Landis
Andrew,
THANK YOU SO SO MUCH for your help with this. I used what you included above; however only 2 or the 8 tests passed. Any suggestions on how I can edit or fix these?

User-added image
Andrew GAndrew G
Hi Meggan
Can you show me the trigger as you implemented it? 
And also just one of the test methods that has failed?
And if you could put in as a code sample to make it easier to read?
User-added image

I'm curious about the too many soql queries error in particular, and i feel the assertion error may all be similar.

Regards
Andrew
 
Meggan LandisMeggan Landis
Hi again Andrew.
The trigger I used is basically what you provided above:

trigger EventConflict on Event (before insert, before update) {
  String thisId;    //an event will always overlap with itself so omit it from the query
  String resource;   //only treat events as conflicts when they share the same resource
  Datetime startDate;
  Datetime endDate;

  List<Id> ownerIds = new List<Id>();
  for (Event newEvent : trigger.new){
    ownerIds.add(newEvent.OwnerId);
  }

  List<Event> events = [SELECT Id, StartDateTime, EndDateTime, OwnerId FROM Event WHERE OwnerId IN :ownerIds];
  Map<Id, List<Event>> eventsByResource = new Map<Id,List<Event>>();
  for (Event e : events ) {
    if (e.OwnerId != null) {
      List<Event> tempList = eventsByResource.get(e.OwnerId);
      if (tempList == null) {
        tempList = new List<Event>();
      }
      tempList.add(e);
      eventsByResource.put(e.OwnerId, tempList);
    }
  }

  for (Event newEvent : trigger.new){
    resource   = newEvent.OwnerId;
    thisId     = newEvent.Id;
    startDate   = newEvent.StartDateTime;
    endDate   = newEvent.EndDateTime;

    if (eventsByResource.containsKey(resource)) {
      List<Event> eventList = eventsByResource.get(resource);
      for (Event e : eventList ) {
        if (e.Id == thisId) {
          //do nothing 
        } else if( startDate <= e.EndDateTime && endDate < e.StartDateTime ) {
          Trigger.new[0].adderror('Warning: Date and time of event overlaps with previously scheduled event');    
        }
      }
    }
  }
}

In the Apex Class, I entered: 

@isTest
private class EventConflictTriggerTest {
    
    @testSetup static void setup() {
        // Create 2 x user records
        User user1 = createUser('user@One.net', 'userOne','test@none.net','userOne');
        Insert user1;
        User user2 = createUser('user@Two.net', 'userTwo','test@none.net','userTwo');
        Insert user2;

        Event e = new Event();
        e.StartDateTime = Datetime.newInstance(2019, 06, 21, 10, 00, 0);
        e.EndDateTime   = Datetime.newInstance(2019, 06, 21, 11, 00, 0);
        e.OwnerId = user1.Id;
        Insert e;

    }

    @isTest static void test_event_insert_start_overlap() {
        // Implement test code
        User user1 = [SELECT Id FROM User WHERE LastName = 'userOne'];

        Event e = new Event();
        e.StartDateTime = Datetime.newInstance(2019, 06, 21, 10, 30, 0);
        e.EndDateTime   = Datetime.newInstance(2019, 06, 21, 11, 30, 0);
        e.OwnerId = user1.Id;

        try {
            insert e;
        } catch (Exception ex) {
            Boolean expectedExceptionThrown = ex.getMessage().contains('Warning: Date and time of event overlaps with previously scheduled event') ? true : false;
            System.assertEquals(true, expectedExceptionThrown);
        }

    }
    
    @isTest static void test_event_insert_end_overlap() {
        // Implement test code
        User user1 = [SELECT Id FROM User WHERE LastName = 'userOne'];

        Event e = new Event();
        e.StartDateTime = Datetime.newInstance(2019, 06, 21, 09, 30, 0);
        e.EndDateTime   = Datetime.newInstance(2019, 06, 21, 10, 30, 0);
        e.OwnerId = user1.Id;

        try {
            insert e;
        } catch (Exception ex) {
            Boolean expectedExceptionThrown = ex.getMessage().contains('Duplicate booking') ? true : false;
            System.assertEquals(true, expectedExceptionThrown);
        }

    }

    @isTest static void test_event_insert_both_overlap() {
        // Implement test code
        User user1 = [SELECT Id FROM User WHERE LastName = 'userOne'];

        Event e = new Event();
        e.StartDateTime = Datetime.newInstance(2019, 06, 21, 10, 15, 0);
        e.EndDateTime   = Datetime.newInstance(2019, 06, 21, 10, 45, 0);
        e.OwnerId = user1.Id;

        try {
            insert e;
        } catch (Exception ex) {
            Boolean expectedExceptionThrown = ex.getMessage().contains('Duplicate booking') ? true : false;
            System.assertEquals(true, expectedExceptionThrown);
        }

    }

    @isTest static void test_event_insert_both_encompass() {
        // Implement test code
        User user1 = [SELECT Id FROM User WHERE LastName = 'userOne'];

        Event e = new Event();
        e.StartDateTime = Datetime.newInstance(2019, 06, 21, 09, 15, 0);
        e.EndDateTime   = Datetime.newInstance(2019, 06, 21, 11, 45, 0);
        e.OwnerId = user1.Id;

        try {
            insert e;
        } catch (Exception ex) {
            Boolean expectedExceptionThrown = ex.getMessage().contains('Duplicate booking') ? true : false;
            System.assertEquals(true, expectedExceptionThrown);
        }

    }

    @isTest static void test_event_update() {
        // Implement test code
        User user1 = [SELECT Id FROM User WHERE LastName = 'userOne'];

        // Implement test code
        Event existingEvent = [SELECT Id, StartDateTime, EndDateTime FROM Event WHERE OwnerId = :user1.Id];
        existingEvent.EndDateTime = Datetime.newInstance(2019, 06, 21, 11, 45, 0);
        try {
            update existingEvent;
        } catch (Exception ex) {
            //should not be an exception
            System.debug('The following exception has occurred: ' + ex.getMessage());
            Boolean expectedExceptionThrown = ex.getMessage().contains('Warning: Date and time of event overlaps with previously scheduled event') ? true : false;
            System.assertEquals(true, expectedExceptionThrown);
        }
        //grab the event and check that the time updated without error
        existingEvent = [SELECT Id, StartDateTime, EndDateTime FROM Event WHERE OwnerId = :user1.Id];
        Datetime expectedDateTime = Datetime.newInstance(2019, 06, 21, 11, 45, 0);
        System.assertEquals(expectedDateTime,existingEvent.EndDateTime);
    }

    @isTest static void test_event_insert_differentUser() {
        // Implement test code
        User user1 = [SELECT Id FROM User WHERE LastName = 'userOne'];
        User user2 = [SELECT Id FROM User WHERE LastName = 'userTwo'];
        //create an event for user two with same times as original test event
        Event e = new Event();
        e.StartDateTime = Datetime.newInstance(2019, 06, 21, 10, 00, 0);
        e.EndDateTime   = Datetime.newInstance(2019, 06, 21, 11, 00, 0);
        e.OwnerId = user2.Id;

        try {
            insert e;
        } catch (Exception ex) {
            //should not be an exception
            System.debug('The following exception has occurred: ' + ex.getMessage());
        }
        //grab the event and check that the time updated without error
        Event userOneEvent = [SELECT Id, StartDateTime, EndDateTime FROM Event WHERE OwnerId = :user1.Id];
        Event userTwoEvent = [SELECT Id, StartDateTime, EndDateTime FROM Event WHERE OwnerId = :user2.Id];
        System.assertEquals(userOneEvent.EndDateTime,userTwoEvent.EndDateTime);

    }

    @isTest static void test_event_insert_no_overlap() {
        // Implement test code
        User user1 = [SELECT Id FROM User WHERE LastName = 'userOne'];

        Event e = new Event();
        e.StartDateTime = Datetime.newInstance(2019, 06, 21, 11, 00, 0);
        e.EndDateTime   = Datetime.newInstance(2019, 06, 21, 11, 30, 0);
        e.OwnerId = user1.Id;

        try {
            insert e;
        } catch (Exception ex) {
            //should not be an exception
            System.debug('The following exception has occurred: ' + ex.getMessage());

        }
        List<Event> userOneEvents = [SELECT Id, StartDateTime, EndDateTime FROM Event WHERE OwnerId = :user1.Id];
        System.assertEquals(2, userOneEvents.size());

    }

    @isTest static void test_event_insert_bulk_no_overlap() {
        // Implement test code
        User user2 = [SELECT Id FROM User WHERE LastName = 'userTwo'];
        
        List<Event> eventList = new List<Event>();
        for (Integer i = 0; i<200; i++){
            Event e = new Event();
            e.StartDateTime = Datetime.newInstance(2019, 06, 21, 11, 00, i);
            e.EndDateTime   = Datetime.newInstance(2019, 06, 21, 11, 30, i+1);
            e.OwnerId = user2.Id;
            eventList.add(e);
        }

        try {
            insert eventList;
        } catch (Exception ex) {
            //should not be an exception
            System.debug('The following exception has occurred: ' + ex.getMessage());

        }
        List<Event> userOneEvents = [SELECT Id, StartDateTime, EndDateTime FROM Event WHERE OwnerId = :user2.Id];
        System.assertEquals(200, userOneEvents.size()); 
    }


    public static User createUser(String uName, String lName, String eAdd, String alias) {
        User user = new User();
        user.Username = uName;
        user.LastName = lName;
        user.Email = eAdd;
        user.Alias = alias;
        user.TimeZoneSidKey = 'America/New_York';
        user.LocaleSidKey = 'en_US';
        user.EmailEncodingKey = 'ISO-8859-1';
        user.ProfileId = [SELECT id FROM Profile WHERE Name='Standard User'].Id;
        user.LanguageLocaleKey = 'en_US';
        return user;
    }

}

An example of hte detail:
Apex Test Result Detail 
Time Started7/2/2019 10:45 AM
ClassEventConflictTriggerTest
Method Nametest_event_insert_both_encompass
Pass/FailFail
Error MessageSystem.AssertException: Assertion Failed: Expected: true, Actual: false
Stack TraceClass.EventConflictTriggerTest.test_event_insert_both_encompass: line 86, column 1

Do these all need to be added as separate classes--is that why this would fail? Sorry to be such an idiot about this, but I GREATLY appreciate your help!!

Megg
Andrew GAndrew G
No worries Meg

The first thing to do is do a find and replace in the test class that I provided.

If you see the original trigger I wrote, the error message is a simple message - 'Duplicate booking'
Your error message reads:

'Warning: Date and time of event overlaps with previously scheduled event'

Do a find & replace on the test class - replace my string with your longer error message.

The other question is then is there another trigger in play on the Event object?

If there is , a simple way to get the test method for the "too many soqls" to fire is to reduce the number of inserted records from 200 to 100.
 
@isTest static void test_event_insert_bulk_no_overlap() {
		// Implement test code
		User user2 = [SELECT Id FROM User WHERE LastName = 'userTwo'];
		
		List<Event> eventList = new List<Event>();
		for (Integer i = 0; i<100; i++){
			Event e = new Event();
			e.StartDateTime = Datetime.newInstance(2019, 06, 21, 11, 00, i);
			e.EndDateTime	= Datetime.newInstance(2019, 06, 21, 11, 30, i+1);
			e.OwnerId = user2.Id;
			eventList.add(e);
		}

just change the number in the line for (Integer i = 0; i<200; i++){  to 100

REgards

​​​​​​​andrew
 
Meggan LandisMeggan Landis
Andrew, ugh, I'm hoping you can still help. So I made all of the changes, and the testing passed in the sandbox. (Yay!)  I thought that I was good to go, unfortunately, when I deployed to production and tried to validate, I got an error message that the code coverage was only 49%. Do you know why it would be fine in the sandbox, but wouldn't pass in production? Any ideas on how to fix this?

THANK YOU!!

Meg