# Janus SDK Testing Guide

This guide explains how to test code that interacts with the Janus SDK implementation.

## Overview

The Janus SDK provides a static interface (`Janus`) as its public API, with the actual implementation in the `JanusImplementation` class. Our testing strategy focuses on:

1. **Testing the implementation directly**: We test `JanusImplementation` rather than the static `Janus` interface
2. **Dependency injection**: We provide mock dependencies to the implementation class and/or use `MockJanusImplementation` for components that depend on Janus
3. **Isolated testing**: Each test focuses on a specific behavior with minimal dependencies

## How to Test Code That Uses Janus SDK

### Using MockJanusImplementation

For code that depends on the `JanusProtocol`, use the `MockJanusImplementation` class:

```swift
// In your tests
class MyFeatureTests: XCTestCase {
    // The feature that depends on Janus
    var myFeature: MyFeature!
    
    // The mock Janus implementation
    var mockJanus: MockJanusImplementation!
    
    override func setUp() {
        super.setUp()
        
        // Create the mock implementation
        mockJanus = MockJanusImplementation()
        
        // Configure the mock as needed
        mockJanus.mockHasExperience = true
        mockJanus.mockConsent = ["analytics": true]
        
        // Create your feature with the mock
        myFeature = MyFeature(janus: mockJanus)
    }
    
    func testFeatureWithExperience() {
        // Configure the mock
        mockJanus.mockHasExperience = true
        
        // Test your feature
        let result = myFeature.doSomething()
        
        // Verify the result
        XCTAssertTrue(result)
        
        // Verify interactions with Janus
        XCTAssertTrue(mockJanus.showExperienceWasCalled)
    }
}
```

### Mocking Specific Behaviors

The `MockJanusImplementation` allows you to:

1. **Control returned values**: Set properties like `mockHasExperience` to control what the mock returns
2. **Verify method calls**: Check properties like `showExperienceWasCalled` to verify interactions
3. **Configure initializations**: Use `shouldInitializeSucceed` and `initializationError` to simulate success/failure
4. **Test event listeners**: Add and trigger event listeners to verify your code handles events properly

## How to Test JanusImplementation Itself

### 1. Setting Up Mock Dependencies

Create mock implementations of the dependencies required by `JanusImplementation`:

```swift
// Mock the ConsentManagerProtocol
class MockConsentManager: ConsentManagerProtocol {
    var mockConsent: [String: Bool] = [:]
    var mockMetadata = ConsentMetadata(createdAt: nil, updatedAt: nil)
    
    // Tracking variables to verify method calls
    var setConsentCalled = false
    var lastSetConsentKey: String?
    var lastSetConsentValue: Bool?
    
    func getConsent() -> [String: Bool] {
        return mockConsent
    }
    
    func getMetadata() -> ConsentMetadata {
        return mockMetadata
    }
    
    func setConsent(value: Bool, forKey key: String) {
        setConsentCalled = true
        lastSetConsentKey = key
        lastSetConsentValue = value
        mockConsent[key] = value
    }
    
    // ... implement other required methods
}

// Mock other dependencies similarly (APIProtocols, etc.)
```

### 2. Injecting Dependencies in Tests

In your test class, instantiate `JanusImplementation` with mock dependencies:

```swift
class JanusImplementationTests: XCTestCase {
    // The actual implementation we're testing
    var janusImpl: JanusImplementation!
    
    // Mock dependencies
    var mockPrivacyExperienceAPI: MockPrivacyExperienceAPIProtocol!
    var mockIPLocationAPI: MockIPLocationAPI!
    var mockConsentManager: MockConsentManager!
    
    override func setUp() {
        super.setUp()
        
        // Create mock dependencies
        mockPrivacyExperienceAPI = MockPrivacyExperienceAPIProtocol()
        mockIPLocationAPI = MockIPLocationAPI()
        mockConsentManager = MockConsentManager()
        
        // Create the actual implementation with mock dependencies
        janusImpl = JanusImplementation(
            privacyExperienceAPI: mockPrivacyExperienceAPI,
            ipLocationAPI: mockIPLocationAPI,
            consentManager: mockConsentManager
        )
    }
    
    // Tests follow...
}
```

### 3. Testing Implementation Behavior

Write tests that verify the behavior of the implementation:

```swift
func testSetConsent() {
    // Test setting a single consent value
    janusImpl.setConsent(value: true, forKey: "test-key", propagateToWebViews: true)
    
    // Verify the consent manager was called correctly
    XCTAssertTrue(mockConsentManager.setConsentCalled)
    XCTAssertEqual(mockConsentManager.lastSetConsentKey, "test-key")
    XCTAssertEqual(mockConsentManager.lastSetConsentValue, true)
}
```

### 4. Testing Async Methods

For asynchronous methods, use expectations:

```swift
func testInitialize_WithRegion() {
    // Configure the mock API response
    let response = PrivacyExperienceResponse(
        items: [testExperienceItem],
        total: 1,
        page: 1,
        pages: 1
    )
    mockPrivacyExperienceAPI.mockResponse = .success(response)
    
    // Create config
    let config = JanusConfiguration(
        apiHost: "https://test.example.com",
        propertyId: "test-property",
        ipLocation: false,
        region: "US-CA"
    )
    
    // Create expectation for async call
    let expectation = XCTestExpectation(description: "Initialize completes")
    
    // Call initialize
    janusImpl.initialize(config: config) { success, error in
        XCTAssertTrue(success)
        XCTAssertNil(error)
        expectation.fulfill()
    }
    
    // Wait for async completion
    wait(for: [expectation], timeout: 1.0)
    
    // Verify API interactions
    XCTAssertTrue(mockPrivacyExperienceAPI.fetchPrivacyExperiencesCalled)
    XCTAssertEqual(mockPrivacyExperienceAPI.lastRegion, "US-CA")
}
```

### 5. Testing Event Listeners

```swift
func testEventListeners() {
    // Add a listener
    var eventReceived = false
    var receivedEvent: JanusEvent?
    
    let listenerId = janusImpl.addConsentEventListener { event in
        eventReceived = true
        receivedEvent = event
    }
    
    // Trigger an event
    janusImpl.notifyEventListeners(event: JanusEvent(type: "testEvent"))
    
    // Verify the listener was called with the correct event
    XCTAssertTrue(eventReceived)
    XCTAssertEqual(receivedEvent?.type, "testEvent")
    
    // Remove the listener and verify it's no longer called
    janusImpl.removeConsentEventListener(listenerId: listenerId)
    
    eventReceived = false
    janusImpl.notifyEventListeners(event: JanusEvent(type: "testEvent"))
    XCTAssertFalse(eventReceived)
}
```

## Best Practices

1. **Keep mocks in a dedicated directory**: Store all mock objects in a `Mocks` folder to maintain organization
2. **Test edge cases**: Include tests for failure conditions and edge cases
3. **Reset mock state between tests**: Either create new mocks in each test or reset their state
4. **Verify interactions**: Check that methods are called with the expected parameters
5. **Focus on behavior**: Test the behavior rather than the implementation details

## Examples

See `JanusImplementationTests.swift` for examples of testing the implementation itself, and `JanusMockTests.swift` for examples of how to use the `MockJanusImplementation` class. 